package sqlite

import (
	"context"
	"database/sql"
	"encoding/json"
	"time"

	"switchboard-core/store"
)

type PackageStore struct{}

func NewPackageStore() *PackageStore { return &PackageStore{} }

// ── Lifecycle ────────────────────────────────────

func (s *PackageStore) Seed(ctx context.Context, id, title, source string, manifest map[string]any) error {
	manifestJSON := ToJSON(manifest)
	_, err := DB.ExecContext(ctx, `
		INSERT INTO packages (id, title, source, manifest, enabled, installed_at, updated_at)
		VALUES (?, ?, ?, ?, 1, datetime('now'), datetime('now'))
		ON CONFLICT (id) DO UPDATE SET
			title = excluded.title,
			manifest = excluded.manifest,
			source = excluded.source,
			updated_at = datetime('now')`,
		id, title, source, manifestJSON)
	return err
}

func (s *PackageStore) List(ctx context.Context) ([]store.PackageRegistration, error) {
	return s.scanMany(ctx, `SELECT `+pkgCols+` FROM packages p ORDER BY p.source, p.title`)
}

func (s *PackageStore) Get(ctx context.Context, id string) (*store.PackageRegistration, error) {
	return s.scanOne(ctx, `SELECT `+pkgCols+` FROM packages p WHERE p.id = ?`, id)
}

func (s *PackageStore) SetEnabled(ctx context.Context, id string, enabled bool) error {
	enabledInt := 0
	if enabled {
		enabledInt = 1
	}
	result, err := DB.ExecContext(ctx,
		`UPDATE packages SET enabled = ?, updated_at = datetime('now') WHERE id = ?`,
		enabledInt, id)
	if err != nil {
		return err
	}
	affected, _ := result.RowsAffected()
	if affected == 0 {
		return sql.ErrNoRows
	}
	return nil
}

func (s *PackageStore) SetStatus(ctx context.Context, id string, status string) error {
	result, err := DB.ExecContext(ctx,
		`UPDATE packages SET status = ?, updated_at = datetime('now') WHERE id = ?`,
		status, id)
	if err != nil {
		return err
	}
	affected, _ := result.RowsAffected()
	if affected == 0 {
		return sql.ErrNoRows
	}
	return nil
}

func (s *PackageStore) Delete(ctx context.Context, id string) error {
	result, err := DB.ExecContext(ctx,
		`DELETE FROM packages WHERE id = ? AND source != 'core'`, id)
	if err != nil {
		return err
	}
	affected, _ := result.RowsAffected()
	if affected == 0 {
		return sql.ErrNoRows
	}
	return nil
}

func (s *PackageStore) ListEnabled(ctx context.Context) ([]string, error) {
	rows, err := DB.QueryContext(ctx,
		`SELECT id FROM packages WHERE enabled = 1 ORDER BY title`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var ids []string
	for rows.Next() {
		var id string
		if err := rows.Scan(&id); err != nil {
			continue
		}
		ids = append(ids, id)
	}
	return ids, rows.Err()
}

// ── Extended lifecycle ───────────────────────────

func (s *PackageStore) Create(ctx context.Context, pkg *store.PackageRegistration) error {
	now := time.Now().UTC()
	manifestJSON := ToJSON(pkg.Manifest)
	if pkg.Status == "" {
		pkg.Status = "active"
	}
	_, err := DB.ExecContext(ctx, `
		INSERT INTO packages (id, title, type, version, description, author, tier,
			is_system, scope, team_id, installed_by, manifest, enabled, status,
			schema_version, package_settings, source,
			installed_at, updated_at)
		VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
		pkg.ID, pkg.Title, pkg.Type, pkg.Version, pkg.Description, pkg.Author,
		pkg.Tier, pkg.IsSystem, pkg.Scope,
		nullStrPtr(pkg.TeamID), nullStrPtr(pkg.InstalledBy),
		manifestJSON, pkg.Enabled, pkg.Status,
		pkg.SchemaVersion, defaultJSON(pkg.PackageSettings),
		pkg.Source,
		now.Format(timeFmt), now.Format(timeFmt),
	)
	if err != nil {
		return err
	}
	pkg.InstalledAt = now.Format(timeFmt)
	pkg.UpdatedAt = now.Format(timeFmt)
	return nil
}

func (s *PackageStore) Update(ctx context.Context, id string, pkg *store.PackageRegistration) error {
	manifestJSON := ToJSON(pkg.Manifest)
	_, err := DB.ExecContext(ctx, `
		UPDATE packages SET
			title = ?, type = ?, version = ?, description = ?, author = ?,
			tier = ?, manifest = ?, is_system = ?, enabled = ?,
			updated_at = datetime('now')
		WHERE id = ?`,
		pkg.Title, pkg.Type, pkg.Version, pkg.Description, pkg.Author,
		pkg.Tier, manifestJSON, pkg.IsSystem, pkg.Enabled, id,
	)
	return err
}

func (s *PackageStore) ListByType(ctx context.Context, pkgType string) ([]store.PackageRegistration, error) {
	return s.scanMany(ctx, `SELECT `+pkgCols+` FROM packages p WHERE p.type = ? ORDER BY p.title`, pkgType)
}

func (s *PackageStore) ListEnabledByType(ctx context.Context, pkgType string) ([]store.PackageRegistration, error) {
	return s.scanMany(ctx,
		`SELECT `+pkgCols+` FROM packages p WHERE p.type = ? AND p.enabled = 1 ORDER BY p.title`,
		pkgType)
}

// ── Extension runtime ────────────────────────────

func (s *PackageStore) ListForUser(ctx context.Context, userID string) ([]store.UserPackage, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT `+pkgCols+`,
			pus.is_enabled AS user_enabled,
			pus.settings AS user_settings
		FROM packages p
		LEFT JOIN package_user_settings pus
			ON pus.package_id = p.id AND pus.user_id = ?
		WHERE p.enabled = 1
			AND p.type IN ('extension', 'full')
			AND (p.is_system = 1 OR COALESCE(pus.is_enabled, 1) = 1)
			AND (
				p.scope = 'global'
				OR (p.scope = 'personal' AND p.installed_by = ?)
				OR (p.scope = 'team' AND p.team_id IN (
					SELECT team_id FROM team_members WHERE user_id = ?
				))
			)
		ORDER BY p.title`, userID, userID, userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var result []store.UserPackage
	for rows.Next() {
		var up store.UserPackage
		var teamID, installedBy sql.NullString
		var manifestJSON string
		var pkgSettings string
		var enabledInt int
		var isSystemInt int
		var userEnabled sql.NullBool
		var userSettings []byte

		if err := rows.Scan(
			&up.ID, &up.Title, &up.Type, &up.Version, &up.Description,
			&up.Author, &up.Tier, &isSystemInt, &up.Scope,
			&teamID, &installedBy,
			&manifestJSON, &enabledInt, &up.Status,
			&up.SchemaVersion, &pkgSettings,
			&up.Source, &up.InstalledAt, &up.UpdatedAt,
			&userEnabled, &userSettings,
		); err != nil {
			return nil, err
		}
		up.IsSystem = isSystemInt != 0
		up.Enabled = enabledInt != 0
		up.TeamID = NullableStringPtr(teamID)
		up.InstalledBy = NullableStringPtr(installedBy)
		json.Unmarshal([]byte(manifestJSON), &up.Manifest)
		up.PackageSettings = json.RawMessage(pkgSettings)
		if userEnabled.Valid {
			up.UserEnabled = &userEnabled.Bool
		}
		if userSettings != nil {
			raw := json.RawMessage(userSettings)
			up.UserSettings = &raw
		}
		result = append(result, up)
	}
	if result == nil {
		result = make([]store.UserPackage, 0)
	}
	return result, rows.Err()
}

func (s *PackageStore) GetUserSettings(ctx context.Context, pkgID, userID string) (*store.PackageUserSettings, error) {
	var pus store.PackageUserSettings
	err := DB.QueryRowContext(ctx, `
		SELECT package_id, user_id, settings, is_enabled
		FROM package_user_settings
		WHERE package_id = ? AND user_id = ?`,
		pkgID, userID,
	).Scan(&pus.PackageID, &pus.UserID, &pus.Settings, &pus.IsEnabled)
	if err != nil {
		return nil, err
	}
	return &pus, nil
}

func (s *PackageStore) SetUserSettings(ctx context.Context, pus *store.PackageUserSettings) error {
	_, err := DB.ExecContext(ctx, `
		INSERT INTO package_user_settings (package_id, user_id, settings, is_enabled)
		VALUES (?, ?, ?, ?)
		ON CONFLICT (package_id, user_id)
		DO UPDATE SET settings = EXCLUDED.settings, is_enabled = EXCLUDED.is_enabled`,
		pus.PackageID, pus.UserID, pus.Settings, pus.IsEnabled,
	)
	return err
}

func (s *PackageStore) DeleteUserSettings(ctx context.Context, pkgID, userID string) error {
	_, err := DB.ExecContext(ctx, `
		DELETE FROM package_user_settings WHERE package_id = ? AND user_id = ?`,
		pkgID, userID,
	)
	return err
}

// ── Helpers ──────────────────────────────────────

const pkgCols = `p.id, p.title, p.type, p.version, p.description, p.author,
	p.tier, p.is_system, p.scope, p.team_id, p.installed_by,
	p.manifest, p.enabled, p.status,
	p.schema_version, p.package_settings,
	p.source, p.installed_at, p.updated_at`

func (s *PackageStore) scanOne(ctx context.Context, query string, args ...interface{}) (*store.PackageRegistration, error) {
	var pkg store.PackageRegistration
	var teamID, installedBy sql.NullString
	var manifestJSON string
	var pkgSettings string
	var enabledInt int
	var isSystemInt int
	err := DB.QueryRowContext(ctx, query, args...).Scan(
		&pkg.ID, &pkg.Title, &pkg.Type, &pkg.Version, &pkg.Description,
		&pkg.Author, &pkg.Tier, &isSystemInt, &pkg.Scope,
		&teamID, &installedBy,
		&manifestJSON, &enabledInt, &pkg.Status,
		&pkg.SchemaVersion, &pkgSettings,
		&pkg.Source, &pkg.InstalledAt, &pkg.UpdatedAt,
	)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	pkg.IsSystem = isSystemInt != 0
	pkg.Enabled = enabledInt != 0
	pkg.TeamID = NullableStringPtr(teamID)
	pkg.InstalledBy = NullableStringPtr(installedBy)
	json.Unmarshal([]byte(manifestJSON), &pkg.Manifest)
	pkg.PackageSettings = json.RawMessage(pkgSettings)
	return &pkg, nil
}

func (s *PackageStore) scanMany(ctx context.Context, query string, args ...interface{}) ([]store.PackageRegistration, error) {
	rows, err := DB.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var result []store.PackageRegistration
	for rows.Next() {
		var pkg store.PackageRegistration
		var teamID, installedBy sql.NullString
		var manifestJSON string
		var pkgSettings string
		var enabledInt int
		var isSystemInt int
		if err := rows.Scan(
			&pkg.ID, &pkg.Title, &pkg.Type, &pkg.Version, &pkg.Description,
			&pkg.Author, &pkg.Tier, &isSystemInt, &pkg.Scope,
			&teamID, &installedBy,
			&manifestJSON, &enabledInt, &pkg.Status,
			&pkg.SchemaVersion, &pkgSettings,
			&pkg.Source, &pkg.InstalledAt, &pkg.UpdatedAt,
		); err != nil {
			return nil, err
		}
		pkg.IsSystem = isSystemInt != 0
		pkg.Enabled = enabledInt != 0
		pkg.TeamID = NullableStringPtr(teamID)
		pkg.InstalledBy = NullableStringPtr(installedBy)
		json.Unmarshal([]byte(manifestJSON), &pkg.Manifest)
		pkg.PackageSettings = json.RawMessage(pkgSettings)
		result = append(result, pkg)
	}
	return result, rows.Err()
}

// ── Scoped visibility (v0.30.0) ──────────────────

func (s *PackageStore) ListVisiblePackages(ctx context.Context, userID string) ([]store.PackageRegistration, error) {
	return s.scanMany(ctx, `
		SELECT `+pkgCols+`
		FROM packages p
		WHERE p.enabled = 1
			AND (
				p.scope = 'global'
				OR (p.scope = 'personal' AND p.installed_by = ?)
				OR (p.scope = 'team' AND p.team_id IN (
					SELECT team_id FROM team_members WHERE user_id = ?
				))
			)
		ORDER BY p.source, p.title`, userID, userID)
}

// ── Package lifecycle (v0.30.0) ──────────────────

func (s *PackageStore) SetSchemaVersion(ctx context.Context, id string, version int) error {
	result, err := DB.ExecContext(ctx,
		`UPDATE packages SET schema_version = ?, updated_at = datetime('now') WHERE id = ?`,
		version, id)
	if err != nil {
		return err
	}
	affected, _ := result.RowsAffected()
	if affected == 0 {
		return sql.ErrNoRows
	}
	return nil
}

func (s *PackageStore) GetPackageSettings(ctx context.Context, id string) (json.RawMessage, error) {
	var settings string
	err := DB.QueryRowContext(ctx,
		`SELECT package_settings FROM packages WHERE id = ?`, id).Scan(&settings)
	if err != nil {
		return nil, err
	}
	return json.RawMessage(settings), nil
}

func (s *PackageStore) SetPackageSettings(ctx context.Context, id string, settings json.RawMessage) error {
	result, err := DB.ExecContext(ctx,
		`UPDATE packages SET package_settings = ?, updated_at = datetime('now') WHERE id = ?`,
		string(settings), id)
	if err != nil {
		return err
	}
	affected, _ := result.RowsAffected()
	if affected == 0 {
		return sql.ErrNoRows
	}
	return nil
}

// ── Team-level settings (v0.2.0) ─────────────────

func (s *PackageStore) GetTeamSettings(ctx context.Context, pkgID, teamID string) (json.RawMessage, error) {
	var settings string
	err := DB.QueryRowContext(ctx,
		`SELECT settings FROM package_team_settings WHERE package_id = ? AND team_id = ?`,
		pkgID, teamID).Scan(&settings)
	if err != nil {
		return nil, err
	}
	return json.RawMessage(settings), nil
}

func (s *PackageStore) SetTeamSettings(ctx context.Context, pkgID, teamID string, settings json.RawMessage) error {
	_, err := DB.ExecContext(ctx, `
		INSERT INTO package_team_settings (package_id, team_id, settings, updated_at)
		VALUES (?, ?, ?, datetime('now'))
		ON CONFLICT (package_id, team_id)
		DO UPDATE SET settings = EXCLUDED.settings, updated_at = datetime('now')`,
		pkgID, teamID, string(settings))
	return err
}

func (s *PackageStore) DeleteTeamSettings(ctx context.Context, pkgID, teamID string) error {
	_, err := DB.ExecContext(ctx,
		`DELETE FROM package_team_settings WHERE package_id = ? AND team_id = ?`,
		pkgID, teamID)
	return err
}

func nullStrPtr(s *string) sql.NullString {
	if s == nil {
		return sql.NullString{}
	}
	return sql.NullString{String: *s, Valid: true}
}

// defaultJSON returns the raw message or '{}' if nil/empty.
func defaultJSON(raw json.RawMessage) string {
	if len(raw) == 0 {
		return "{}"
	}
	return string(raw)
}
