package store

import (
	"context"
	"errors"
	"time"

	"switchboard-core/models"
)

// ── Sentinel Errors ─────────────────────────

// ErrSystemGroup is returned when attempting to delete a system-sourced group.
var ErrSystemGroup = errors.New("system groups cannot be deleted")

// =========================================
// STORES — Data Access Layer
// =========================================
// Every database operation goes through these interfaces.
// Handlers never touch SQL directly. This makes the DB
// portable (Postgres today, SQLite/MySQL later) and
// handlers testable with in-memory implementations.
// =========================================

// Stores bundles all store interfaces for dependency injection.
// PolicyStore provides platform policy flags (allow_registration, etc.)
type PolicyStore interface {
	GetBool(ctx context.Context, key string) (bool, error)
	SetBool(ctx context.Context, key string, val bool) error
	Get(ctx context.Context, key string) (string, error)
	Set(ctx context.Context, key, value string) error
	GetAll(ctx context.Context) (map[string]string, error)
}

type Stores struct {
	Users         UserStore
	Teams         TeamStore
	Audit         AuditStore
	GlobalConfig  GlobalConfigStore
	Policies       PolicyStore
	Groups         GroupStore
	ResourceGrants ResourceGrantStore
	Notifications  NotificationStore
	NotifPrefs     NotificationPreferenceStore
	Presence        PresenceStore          // v0.29.0: User online/offline status
	Health          HealthStore            // v0.29.0: Provider health window management
	Connections     ConnectionStore          // v0.38.1: Extension connection credentials
	Dependencies    DependencyStore          // v0.38.2: Library package dependency graph
	Packages        PackageStore           // v0.28.7: Unified package registry (surfaces + extensions)
	Workflows       WorkflowStore          // v0.26.1: Workflow definitions + stages
	ExtPermissions  ExtensionPermissionStore // v0.29.0: Extension declared/granted capabilities
	ExtData         ExtDataStore             // v0.29.2: Extension namespaced table catalog
	Tickets         TicketStore              // v0.32.0: WS auth tickets (PG-backed for cross-pod)
	RateLimits      RateLimitStore           // v0.32.0: Distributed rate limiting
	Triggers        TriggerStore             // v0.2.2: Extension event/webhook triggers
	ScheduledTasks  ScheduledTaskStore       // v0.2.2: User-created cron tasks
}

// TeamAvailableModel is returned by CatalogStore.ListTeamAvailable.
// CatalogSyncEntry is the input format from provider FetchModels.
// =========================================
// USER STORE
// =========================================

type UserStore interface {
	Create(ctx context.Context, u *models.User) error
	GetByID(ctx context.Context, id string) (*models.User, error)
	GetByUsername(ctx context.Context, username string) (*models.User, error)
	GetByEmail(ctx context.Context, email string) (*models.User, error)
	GetByLogin(ctx context.Context, login string) (*models.User, error) // username or email
	GetByHandle(ctx context.Context, handle string) (*models.User, error)
	GetByExternalID(ctx context.Context, authSource, externalID string) (*models.User, error)
	Update(ctx context.Context, id string, fields map[string]interface{}) error
	Delete(ctx context.Context, id string) error
	List(ctx context.Context, opts ListOptions) ([]models.User, int, error)
	UpdateLastLogin(ctx context.Context, id string) error
	SetActive(ctx context.Context, id string, active bool) error
	ListActiveUserIDs(ctx context.Context) ([]string, error)

	// Refresh tokens
	CreateRefreshToken(ctx context.Context, userID, tokenHash string, expiresAt time.Time) error
	GetRefreshToken(ctx context.Context, tokenHash string) (userID string, err error)
	RevokeRefreshToken(ctx context.Context, tokenHash string) error
	RevokeAllRefreshTokens(ctx context.Context, userID string) error
	CleanExpiredTokens(ctx context.Context) error

	// ── CS1 additions (v0.29.0) ──

	// Exists returns true if a user with the given ID exists and is active.
	Exists(ctx context.Context, userID string) (bool, error)

	// SearchActive returns users matching a query (username, display_name, handle).
	// Excludes the calling user. Max 20 results.
	SearchActive(ctx context.Context, excludeUserID, query string) ([]UserSearchResult, error)

	// ── CS2 additions (v0.29.0) ──

	// CountAll returns the total number of users.
	CountAll(ctx context.Context) (int, error)

	// MergeSettings performs a dialect-aware JSON merge into the user's settings column.
	MergeSettings(ctx context.Context, userID string, patch []byte) error

	// GetVaultKeys returns the user's vault encryption state.
	GetVaultKeys(ctx context.Context, userID string) (vaultSet bool, encUEK, salt, nonce []byte, err error)

	// UpdateVaultKeys re-wraps the user's vault keys (password change).
	UpdateVaultKeys(ctx context.Context, userID string, encUEK, salt, nonce []byte) error

	// ── CS4 additions (v0.29.0) ──

	// ClearVaultKeys nulls out vault columns and sets vault_set = false.
	ClearVaultKeys(ctx context.Context, userID string) error

	// InitVaultKeys stores vault keys and sets vault_set = true (first-time init).
	InitVaultKeys(ctx context.Context, userID string, encUEK, salt, nonce []byte) error
}

// UserSearchResult is a lightweight result for user search.
type UserSearchResult struct {
	ID          string `json:"id"`
	Username    string `json:"username"`
	DisplayName string `json:"display_name"`
	Handle      string `json:"handle"`
}

// =========================================
// TEAM STORE
// =========================================

type TeamStore interface {
	Create(ctx context.Context, t *models.Team) error
	GetByID(ctx context.Context, id string) (*models.Team, error)
	Update(ctx context.Context, id string, fields map[string]interface{}) error
	Delete(ctx context.Context, id string) error
	List(ctx context.Context) ([]models.Team, error)
	ListForUser(ctx context.Context, userID string) ([]models.Team, error) // active teams with MyRole

	// Members
	AddMember(ctx context.Context, teamID, userID, role string) error
	RemoveMember(ctx context.Context, teamID, userID string) error
	UpdateMemberRole(ctx context.Context, teamID, userID, role string) error
	ListMembers(ctx context.Context, teamID string) ([]models.TeamMember, error)
	GetMember(ctx context.Context, teamID, userID string) (*models.TeamMember, error)
	GetUserTeamIDs(ctx context.Context, userID string) ([]string, error)
	IsTeamAdmin(ctx context.Context, teamID, userID string) (bool, error)
	IsMember(ctx context.Context, teamID, userID string) (bool, error)

	// ── CS1 additions (v0.29.0) ──

	// Exists returns true if a team with the given ID exists.
	Exists(ctx context.Context, teamID string) (bool, error)

	// UpdateMemberRoleByID updates a member's role using the member row ID.
	UpdateMemberRoleByID(ctx context.Context, memberID, teamID, role string) (int64, error)

	// DeleteMemberByID deletes a member using the member row ID.
	DeleteMemberByID(ctx context.Context, memberID, teamID string) (int64, error)

	// ListTeamAuditActions returns distinct audit actions for a team's members.
	ListTeamAuditActions(ctx context.Context, teamID string) ([]string, error)

	// ── CS5b additions (v0.29.0) ──

	// GetFirstTeamIDForUser returns the first team_id the user belongs to, or "" if none.
	GetFirstTeamIDForUser(ctx context.Context, userID string) (string, error)

	// ── CS6 additions (v0.29.0) ──

	// AddMemberReturningID inserts a team member and returns the row ID.
	AddMemberReturningID(ctx context.Context, teamID, userID, role string) (string, error)

	// MergeSettings merges a JSON string into the team's settings column.
	MergeSettings(ctx context.Context, teamID, settingsJSON string) error
}

// ChannelListFilter holds filter options for ChannelStore.ListFiltered.
// ChannelListItem is returned by ListFiltered and GetForUser.
// ArchivedChannel is a view model for the admin archived channels list.
// MessageWithSender is returned by MessageStore.ListWithSenderInfo.
// ChannelSearchResult is returned by MessageStore.SearchInChannel.
// =========================================
// AUDIT STORE
// =========================================

type AuditStore interface {
	Log(ctx context.Context, entry *models.AuditEntry) error
	List(ctx context.Context, opts AuditListOptions) ([]models.AuditEntry, int, error)
	ListActions(ctx context.Context) ([]string, error)
}

type AuditListOptions struct {
	ListOptions
	ActorID      string
	Action       string
	ResourceType string
	ResourceID   string
	Since        *time.Time
	Until        *time.Time
	TeamID       string // CS6: scope to team members
}

// NoteSearchResult is returned by NoteStore.SearchKeyword and SearchSemantic.
// FolderInfo is returned by NoteStore.ListFolders.
// =========================================
// GLOBAL CONFIG STORE
// =========================================

type GlobalConfigStore interface {
	Get(ctx context.Context, key string) (models.JSONMap, error)
	Set(ctx context.Context, key string, value models.JSONMap, updatedBy string) error
	GetAll(ctx context.Context) (map[string]models.JSONMap, error)

	// ── OIDC state (v0.29.0-cs4) ──

	// SaveOIDCState stores a state+nonce pair for OIDC callback verification.
	SaveOIDCState(ctx context.Context, state, nonce, redirectTo string) error

	// ConsumeOIDCState retrieves and deletes an OIDC state (one-time use).
	// Returns (nonce, redirectTo, error). Returns sql.ErrNoRows if not found.
	ConsumeOIDCState(ctx context.Context, state string) (nonce, redirectTo string, err error)

	// CleanupOIDCState removes stale OIDC states older than 10 minutes.
	CleanupOIDCState(ctx context.Context) error

	// ── CS6 additions (v0.29.0) ──

	// GetString returns the raw value column as a string for simple settings
	// (e.g. bare JSON booleans like "true"/"false").
	GetString(ctx context.Context, key string) (string, error)
}

// =========================================
// GROUP STORE (v0.16.0)
// =========================================

type GroupStore interface {
	Create(ctx context.Context, g *models.Group) error
	GetByID(ctx context.Context, id string) (*models.Group, error)
	Update(ctx context.Context, id string, patch models.GroupPatch) error
	Delete(ctx context.Context, id string) error

	// Scoped listing
	ListAll(ctx context.Context) ([]models.Group, error)           // admin
	ListForTeam(ctx context.Context, teamID string) ([]models.Group, error) // team admin
	ListForUser(ctx context.Context, userID string) ([]models.Group, error) // groups I belong to

	// Members
	AddMember(ctx context.Context, groupID, userID, addedBy string) error
	RemoveMember(ctx context.Context, groupID, userID string) error
	ListMembers(ctx context.Context, groupID string) ([]models.GroupMember, error)
	IsMember(ctx context.Context, groupID, userID string) (bool, error)

	// For access resolution: returns group IDs a user belongs to
	GetUserGroupIDs(ctx context.Context, userID string) ([]string, error)
}

// =========================================
// RESOURCE GRANT STORE (v0.16.0)
// =========================================

type ResourceGrantStore interface {
	Set(ctx context.Context, grant *models.ResourceGrant) error
	Get(ctx context.Context, resourceType, resourceID string) (*models.ResourceGrant, error)
	Delete(ctx context.Context, resourceType, resourceID string) error

	// Access check: does userID have group-based access to this resource?
	UserHasGroupAccess(ctx context.Context, userID, resourceType, resourceID string) (bool, error)
}

// =========================================
// NOTIFICATION STORE (v0.20.0)
// =========================================

type NotificationStore interface {
	Create(ctx context.Context, n *models.Notification) error
	ListByUser(ctx context.Context, userID string, limit, offset int, unreadOnly bool) ([]models.Notification, int, error)
	MarkRead(ctx context.Context, id, userID string) error
	MarkAllRead(ctx context.Context, userID string) error
	Delete(ctx context.Context, id, userID string) error
	UnreadCount(ctx context.Context, userID string) (int, error)
	DeleteOlderThan(ctx context.Context, before time.Time) (int64, error)
}

// =========================================
// NOTIFICATION PREFERENCES STORE (v0.20.0)
// =========================================

type NotificationPreferenceStore interface {
	// Get returns the preference for a specific user + type. Returns nil if not set.
	Get(ctx context.Context, userID, notifType string) (*models.NotificationPreference, error)
	// ListForUser returns all preferences set by a user.
	ListForUser(ctx context.Context, userID string) ([]models.NotificationPreference, error)
	// Upsert creates or updates a preference row.
	Upsert(ctx context.Context, pref *models.NotificationPreference) error
	// Delete removes a preference (falls back to default).
	Delete(ctx context.Context, userID, notifType string) error
}

// =========================================
// SESSION STORE (v0.24.3)
// =========================================


// =========================================
// HEALTH STORE (v0.29.0)
// =========================================

// HealthStore manages provider health windows. The full interface is used
// by the health accumulator and status querier; the scheduler only needs Prune.
type HealthStore interface {
	// Prune deletes health windows older than the given time. Returns rows affected.
	Prune(ctx context.Context, before time.Time) (int64, error)
}

// =========================================
// PRESENCE STORE (v0.29.0)
// =========================================

// PresenceStore manages user online/offline status.
type PresenceStore interface {
	// Heartbeat upserts the user's last_seen timestamp to now.
	Heartbeat(ctx context.Context, userID string) error

	// GetLastSeen returns the user's last heartbeat time, or nil if never seen.
	GetLastSeen(ctx context.Context, userID string) (*time.Time, error)

	// GetStatuses returns online/offline status for a list of user IDs.
	// Users with last_seen after threshold are "online", otherwise "offline".
	GetStatuses(ctx context.Context, userIDs []string, threshold time.Time) (map[string]string, error)
}

// =========================================
// SHARED TYPES
// =========================================
// CONNECTION STORE (v0.38.1)
// =========================================

type ConnectionStore interface {
	Create(ctx context.Context, conn *models.ExtConnection) error
	GetByID(ctx context.Context, id string) (*models.ExtConnection, error)
	Update(ctx context.Context, id string, patch models.ExtConnectionPatch) error
	Delete(ctx context.Context, id string) error

	// Scoped queries
	ListGlobal(ctx context.Context) ([]models.ExtConnection, error)
	ListForTeam(ctx context.Context, teamID string) ([]models.ExtConnection, error)
	ListForUser(ctx context.Context, userID string) ([]models.ExtConnection, error)

	// Resolution chain: personal → team → global.
	// Returns first active connection matching type (and optional name).
	// Empty name means "first match". Returns sql.ErrNoRows if not found.
	Resolve(ctx context.Context, userID, connType, name string) (*models.ExtConnection, error)

	// ResolveAll returns all active connections of the given type accessible
	// to the user (personal + team + global).
	ResolveAll(ctx context.Context, userID, connType string) ([]models.ExtConnection, error)

	// ListAccessible returns all connections accessible to the user across
	// all types: personal + team memberships + global.
	ListAccessible(ctx context.Context, userID string) ([]models.ExtConnection, error)

	// DeleteByIDAndScope deletes a connection only if it matches the given
	// scope and owner. Returns rows affected (0 or 1).
	DeleteByIDAndScope(ctx context.Context, id, scope, ownerID string) (int64, error)
}

// =========================================
// DEPENDENCY STORE (v0.38.2)
// =========================================

type DependencyStore interface {
	// Create records that consumerID depends on libraryID.
	Create(ctx context.Context, dep *models.ExtDependency) error

	// Delete removes a single dependency record.
	Delete(ctx context.Context, consumerID, libraryID string) error

	// DeleteAllForConsumer removes all dependency records for a consumer.
	// Called when uninstalling a consumer package.
	DeleteAllForConsumer(ctx context.Context, consumerID string) error

	// ListByConsumer returns all libraries that consumerID depends on.
	ListByConsumer(ctx context.Context, consumerID string) ([]models.ExtDependency, error)

	// ListByLibrary returns all consumers that depend on libraryID.
	ListByLibrary(ctx context.Context, libraryID string) ([]models.ExtDependency, error)

	// ListAll returns the full dependency graph.
	ListAll(ctx context.Context) ([]models.ExtDependency, error)

	// HasConsumers returns true if any package depends on libraryID.
	// Used for uninstall protection.
	HasConsumers(ctx context.Context, libraryID string) (bool, error)
}

// =========================================

// ListOptions provides standard pagination/sort for list queries.
type ListOptions struct {
	Limit  int
	Offset int
	Sort   string // column name
	Order  string // "asc" or "desc"
}

// DefaultListOptions returns sensible defaults.
func DefaultListOptions() ListOptions {
	return ListOptions{
		Limit: 50,
		Order: "desc",
	}
}
