package postgres

import (
	"context"
	"database/sql"
	"fmt"
	"strings"
	"time"

	"switchboard-core/models"
	"switchboard-core/store"
)

type UserStore struct{}

func NewUserStore() *UserStore { return &UserStore{} }

const userCols = `id, username, email, password_hash, display_name, avatar_url,
	is_active, settings, created_at, updated_at, last_login_at,
	auth_source, external_id, handle`

const userListCols = `id, username, email, display_name, avatar_url, is_active,
	settings, created_at, updated_at, last_login_at, auth_source, external_id, handle`

func (s *UserStore) Create(ctx context.Context, u *models.User) error {
	if u.AuthSource == "" {
		u.AuthSource = "builtin"
	}
	return DB.QueryRowContext(ctx, `
		INSERT INTO users (username, email, password_hash, display_name, is_active, settings, auth_source, external_id, handle)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
		RETURNING id, created_at, updated_at`,
		u.Username, u.Email, u.PasswordHash, u.DisplayName, u.IsActive,
		ToJSON(u.Settings), u.AuthSource, u.ExternalID, u.Handle,
	).Scan(&u.ID, &u.CreatedAt, &u.UpdatedAt)
}

func (s *UserStore) GetByID(ctx context.Context, id string) (*models.User, error) {
	return scanOneUser(ctx, fmt.Sprintf("SELECT %s FROM users WHERE id = $1", userCols), id)
}
func (s *UserStore) GetByUsername(ctx context.Context, username string) (*models.User, error) {
	return scanOneUser(ctx, fmt.Sprintf("SELECT %s FROM users WHERE LOWER(username) = LOWER($1)", userCols), username)
}
func (s *UserStore) GetByEmail(ctx context.Context, email string) (*models.User, error) {
	return scanOneUser(ctx, fmt.Sprintf("SELECT %s FROM users WHERE LOWER(email) = LOWER($1)", userCols), email)
}
func (s *UserStore) GetByLogin(ctx context.Context, login string) (*models.User, error) {
	return scanOneUser(ctx, fmt.Sprintf("SELECT %s FROM users WHERE LOWER(username) = LOWER($1) OR LOWER(email) = LOWER($1)", userCols), login)
}
func (s *UserStore) GetByHandle(ctx context.Context, handle string) (*models.User, error) {
	return scanOneUser(ctx, fmt.Sprintf("SELECT %s FROM users WHERE LOWER(handle) = LOWER($1)", userCols), handle)
}
func (s *UserStore) GetByExternalID(ctx context.Context, authSource, externalID string) (*models.User, error) {
	return scanOneUser(ctx, fmt.Sprintf("SELECT %s FROM users WHERE auth_source = $1 AND external_id = $2", userCols), authSource, externalID)
}

func (s *UserStore) Update(ctx context.Context, id string, fields map[string]interface{}) error {
	b := NewUpdate("users")
	for k, v := range fields {
		if k == "settings" {
			b.SetJSON(k, v)
		} else {
			b.Set(k, v)
		}
	}
	if !b.HasSets() {
		return nil
	}
	b.Where("id", id)
	_, err := b.Exec(DB)
	return err
}

func (s *UserStore) Delete(ctx context.Context, id string) error {
	_, err := DB.ExecContext(ctx, "DELETE FROM users WHERE id = $1", id)
	return err
}

func (s *UserStore) List(ctx context.Context, opts store.ListOptions) ([]models.User, int, error) {
	b := NewSelect(userListCols, "users")
	if opts.Sort == "" {
		b.OrderBy("username", "ASC")
	}
	b.Paginate(opts)

	var total int
	DB.QueryRowContext(ctx, "SELECT COUNT(*) FROM users").Scan(&total)

	q, args := b.Build()
	rows, err := DB.QueryContext(ctx, q, args...)
	if err != nil {
		return nil, 0, err
	}
	defer rows.Close()

	var result []models.User
	for rows.Next() {
		var u models.User
		var dn, av sql.NullString
		var extID, hdl sql.NullString
		var sj []byte
		if err := rows.Scan(&u.ID, &u.Username, &u.Email, &dn, &av,
			&u.IsActive, &sj, &u.CreatedAt, &u.UpdatedAt, &u.LastLoginAt,
			&u.AuthSource, &extID, &hdl); err != nil {
			return nil, 0, err
		}
		u.DisplayName = NullableString(dn)
		u.AvatarURL = NullableString(av)
		if extID.Valid { u.ExternalID = &extID.String }
		u.Handle = NullableString(hdl)
		ScanJSON(sj, &u.Settings)
		result = append(result, u)
	}
	return result, total, rows.Err()
}

func (s *UserStore) UpdateLastLogin(ctx context.Context, id string) error {
	_, err := DB.ExecContext(ctx, "UPDATE users SET last_login_at = NOW() WHERE id = $1", id)
	return err
}
func (s *UserStore) SetActive(ctx context.Context, id string, active bool) error {
	_, err := DB.ExecContext(ctx, "UPDATE users SET is_active = $1 WHERE id = $2", active, id)
	return err
}

func (s *UserStore) ListActiveUserIDs(ctx context.Context) ([]string, error) {
	rows, err := DB.QueryContext(ctx, "SELECT id FROM users WHERE is_active = true")
	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 {
			return nil, err
		}
		ids = append(ids, id)
	}
	return ids, rows.Err()
}

// ── Refresh Tokens ──────────────────────────

func (s *UserStore) CreateRefreshToken(ctx context.Context, userID, tokenHash string, expiresAt time.Time) error {
	_, err := DB.ExecContext(ctx, `INSERT INTO refresh_tokens (user_id, token_hash, expires_at) VALUES ($1, $2, $3)`, userID, tokenHash, expiresAt)
	return err
}
func (s *UserStore) GetRefreshToken(ctx context.Context, tokenHash string) (string, error) {
	var userID string
	err := DB.QueryRowContext(ctx, `SELECT user_id FROM refresh_tokens WHERE token_hash = $1 AND revoked_at IS NULL AND expires_at > NOW()`, tokenHash).Scan(&userID)
	return userID, err
}
func (s *UserStore) RevokeRefreshToken(ctx context.Context, tokenHash string) error {
	_, err := DB.ExecContext(ctx, "UPDATE refresh_tokens SET revoked_at = NOW() WHERE token_hash = $1", tokenHash)
	return err
}
func (s *UserStore) RevokeAllRefreshTokens(ctx context.Context, userID string) error {
	_, err := DB.ExecContext(ctx, "UPDATE refresh_tokens SET revoked_at = NOW() WHERE user_id = $1 AND revoked_at IS NULL", userID)
	return err
}
func (s *UserStore) CleanExpiredTokens(ctx context.Context) error {
	_, err := DB.ExecContext(ctx, "DELETE FROM refresh_tokens WHERE expires_at < NOW() - INTERVAL '30 days'")
	return err
}

// ── Internal ────────────────────────────────

func scanOneUser(ctx context.Context, query string, args ...interface{}) (*models.User, error) {
	var u models.User
	var dn, av, ph sql.NullString
	var extID, hdl sql.NullString
	var sj []byte
	err := DB.QueryRowContext(ctx, query, args...).Scan(
		&u.ID, &u.Username, &u.Email, &ph, &dn, &av,
		&u.IsActive, &sj, &u.CreatedAt, &u.UpdatedAt, &u.LastLoginAt,
		&u.AuthSource, &extID, &hdl,
	)
	if err != nil {
		return nil, err
	}
	u.PasswordHash = NullableString(ph)
	u.DisplayName = NullableString(dn)
	u.AvatarURL = NullableString(av)
	if extID.Valid { u.ExternalID = &extID.String }
	u.Handle = NullableString(hdl)
	ScanJSON(sj, &u.Settings)
	return &u, nil
}
// ── CS1 additions (v0.29.0) ─────────────────────────────────────────────

func (s *UserStore) Exists(ctx context.Context, userID string) (bool, error) {
	var exists bool
	err := DB.QueryRowContext(ctx,
		`SELECT EXISTS(SELECT 1 FROM users WHERE id = $1)`, userID).Scan(&exists)
	return exists, err
}

func (s *UserStore) SearchActive(ctx context.Context, excludeUserID, query string) ([]store.UserSearchResult, error) {
	q := `
		SELECT id, username, COALESCE(display_name, '') AS display_name, COALESCE(handle, '') AS handle
		FROM users
		WHERE is_active = true AND id != $1`
	args := []interface{}{excludeUserID}

	if query != "" {
		q += ` AND (LOWER(username) LIKE $2 OR LOWER(display_name) LIKE $3 OR LOWER(handle) LIKE $4)`
		pattern := "%" + strings.ToLower(query) + "%"
		args = append(args, pattern, pattern, pattern)
	}

	q += ` ORDER BY username LIMIT 20`

	rows, err := DB.QueryContext(ctx, q, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var results []store.UserSearchResult
	for rows.Next() {
		var u store.UserSearchResult
		if err := rows.Scan(&u.ID, &u.Username, &u.DisplayName, &u.Handle); err != nil {
			continue
		}
		results = append(results, u)
	}
	if results == nil {
		results = []store.UserSearchResult{}
	}
	return results, rows.Err()
}

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

func (s *UserStore) MergeSettings(ctx context.Context, userID string, patch []byte) error {
	_, err := DB.ExecContext(ctx, `
		UPDATE users SET settings = (
			CASE WHEN settings IS NULL OR settings = 'null'::jsonb OR jsonb_typeof(settings) != 'object'
				THEN '{}'::jsonb ELSE settings END
			) || $1::jsonb, updated_at = NOW() WHERE id = $2
	`, string(patch), userID)
	return err
}

func (s *UserStore) GetVaultKeys(ctx context.Context, userID string) (bool, []byte, []byte, []byte, error) {
	var vaultSet bool
	var encUEK, salt, nonce []byte
	err := DB.QueryRowContext(ctx, `
		SELECT vault_set, encrypted_uek, uek_salt, uek_nonce
		FROM users WHERE id = $1
	`, userID).Scan(&vaultSet, &encUEK, &salt, &nonce)
	return vaultSet, encUEK, salt, nonce, err
}

func (s *UserStore) UpdateVaultKeys(ctx context.Context, userID string, encUEK, salt, nonce []byte) error {
	_, err := DB.ExecContext(ctx, `
		UPDATE users
		SET encrypted_uek = $1, uek_salt = $2, uek_nonce = $3, updated_at = NOW()
		WHERE id = $4
	`, encUEK, salt, nonce, userID)
	return err
}

func (s *UserStore) CountAll(ctx context.Context) (int, error) {
	var count int
	err := DB.QueryRowContext(ctx, "SELECT COUNT(*) FROM users").Scan(&count)
	return count, err
}

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

func (s *UserStore) ClearVaultKeys(ctx context.Context, userID string) error {
	_, err := DB.ExecContext(ctx, `
		UPDATE users
		SET encrypted_uek = NULL, uek_salt = NULL, uek_nonce = NULL, vault_set = false
		WHERE id = $1
	`, userID)
	return err
}

func (s *UserStore) InitVaultKeys(ctx context.Context, userID string, encUEK, salt, nonce []byte) error {
	_, err := DB.ExecContext(ctx, `
		UPDATE users
		SET encrypted_uek = $1, uek_salt = $2, uek_nonce = $3, vault_set = true
		WHERE id = $4
	`, encUEK, salt, nonce, userID)
	return err
}
