package sqlite

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

	"chat-switchboard/models"
	"chat-switchboard/store"
)

type ProviderStore struct{}

func NewProviderStore() *ProviderStore { return &ProviderStore{} }

const providerCols = `id, scope, owner_id, name, provider, endpoint, api_key_enc,
	key_nonce, key_scope, model_default, config, headers, settings, is_active, is_private, created_at, updated_at`

func (s *ProviderStore) Create(ctx context.Context, cfg *models.ProviderConfig) error {
	cfg.ID = store.NewID()
	now := time.Now().UTC()
	cfg.CreatedAt = now
	cfg.UpdatedAt = now
	_, err := DB.ExecContext(ctx, `
		INSERT INTO provider_configs (id, scope, owner_id, name, provider, endpoint, api_key_enc,
			key_nonce, key_scope, model_default, config, headers, settings, is_active, is_private,
			created_at, updated_at)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		cfg.ID, cfg.Scope, models.NullString(cfg.OwnerID), cfg.Name, cfg.Provider, cfg.Endpoint,
		cfg.APIKeyEnc, cfg.KeyNonce, cfg.KeyScope, cfg.ModelDefault,
		ToJSON(cfg.Config), ToJSON(cfg.Headers), ToJSON(cfg.Settings),
		cfg.IsActive, cfg.IsPrivate,
		now.Format(timeFmt), now.Format(timeFmt),
	)
	return err
}

func (s *ProviderStore) GetByID(ctx context.Context, id string) (*models.ProviderConfig, error) {
	row := DB.QueryRowContext(ctx,
		fmt.Sprintf("SELECT %s FROM provider_configs WHERE id = ?", providerCols), id)
	return scanProvider(row)
}

func (s *ProviderStore) Update(ctx context.Context, id string, patch models.ProviderConfigPatch) error {
	b := NewUpdate("provider_configs")
	if patch.Name != nil {
		b.Set("name", *patch.Name)
	}
	if patch.Endpoint != nil {
		b.Set("endpoint", *patch.Endpoint)
	}
	if patch.APIKeyEnc != nil {
		b.Set("api_key_enc", patch.APIKeyEnc)
		b.Set("key_nonce", patch.KeyNonce)
	}
	if patch.ModelDefault != nil {
		b.Set("model_default", *patch.ModelDefault)
	}
	if patch.Config != nil {
		b.SetJSON("config", patch.Config)
	}
	if patch.Headers != nil {
		b.SetJSON("headers", patch.Headers)
	}
	if patch.Settings != nil {
		b.SetJSON("settings", patch.Settings)
	}
	if patch.IsActive != nil {
		b.Set("is_active", *patch.IsActive)
	}
	if patch.IsPrivate != nil {
		b.Set("is_private", *patch.IsPrivate)
	}
	if !b.HasSets() {
		return nil
	}
	b.Where("id", id)
	_, err := b.Exec(DB)
	return err
}

func (s *ProviderStore) Delete(ctx context.Context, id string) error {
	_, err := DB.ExecContext(ctx, "DELETE FROM provider_configs WHERE id = ?", id)
	return err
}

func (s *ProviderStore) ListGlobal(ctx context.Context) ([]models.ProviderConfig, error) {
	return s.listByScope(ctx, models.ScopeGlobal, "")
}

func (s *ProviderStore) ListForTeam(ctx context.Context, teamID string) ([]models.ProviderConfig, error) {
	return s.listByScope(ctx, models.ScopeTeam, teamID)
}

func (s *ProviderStore) ListForUser(ctx context.Context, userID string) ([]models.ProviderConfig, error) {
	return s.listByScope(ctx, models.ScopePersonal, userID)
}

// ListAccessible returns all provider configs a user can access:
// global + team (for teams they belong to) + personal.
func (s *ProviderStore) ListAccessible(ctx context.Context, userID string) ([]models.ProviderConfig, error) {
	rows, err := DB.QueryContext(ctx, fmt.Sprintf(`
		SELECT %s FROM provider_configs
		WHERE is_active = 1 AND (
			scope = 'global'
			OR (scope = 'personal' AND owner_id = ?)
			OR (scope = 'team' AND owner_id IN (
				SELECT team_id FROM team_members WHERE user_id = ?
			))
		)
		ORDER BY scope, name`, providerCols), userID, userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	return scanProviders(rows)
}

// UserCanAccess checks if a user can use a specific provider config.
func (s *ProviderStore) UserCanAccess(ctx context.Context, userID, configID string) (bool, error) {
	var exists bool
	err := DB.QueryRowContext(ctx, `
		SELECT EXISTS(
			SELECT 1 FROM provider_configs
			WHERE id = ? AND is_active = 1 AND (
				scope = 'global'
				OR (scope = 'personal' AND owner_id = ?)
				OR (scope = 'team' AND owner_id IN (
					SELECT team_id FROM team_members WHERE user_id = ?
				))
			)
		)`, configID, userID, userID).Scan(&exists)
	return exists, err
}

// ── Internal helpers ────────────────────────

func (s *ProviderStore) listByScope(ctx context.Context, scope, ownerID string) ([]models.ProviderConfig, error) {
	var rows *sql.Rows
	var err error
	if scope == models.ScopeGlobal {
		rows, err = DB.QueryContext(ctx,
			fmt.Sprintf("SELECT %s FROM provider_configs WHERE scope = ? AND is_active = 1 ORDER BY name", providerCols),
			scope)
	} else {
		rows, err = DB.QueryContext(ctx,
			fmt.Sprintf("SELECT %s FROM provider_configs WHERE scope = ? AND owner_id = ? AND is_active = 1 ORDER BY name", providerCols),
			scope, ownerID)
	}
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	return scanProviders(rows)
}

func scanProvider(row *sql.Row) (*models.ProviderConfig, error) {
	var p models.ProviderConfig
	var ownerID, modelDefault, keyScope sql.NullString
	var configJSON, headersJSON, settingsJSON []byte
	err := row.Scan(
		&p.ID, &p.Scope, &ownerID, &p.Name, &p.Provider, &p.Endpoint,
		&p.APIKeyEnc, &p.KeyNonce, &keyScope, &modelDefault,
		&configJSON, &headersJSON, &settingsJSON,
		&p.IsActive, &p.IsPrivate, st(&p.CreatedAt), st(&p.UpdatedAt),
	)
	if err != nil {
		return nil, err
	}
	p.OwnerID = NullableStringPtr(ownerID)
	p.ModelDefault = modelDefault.String
	p.KeyScope = keyScope.String
	json.Unmarshal(configJSON, &p.Config)
	json.Unmarshal(headersJSON, &p.Headers)
	json.Unmarshal(settingsJSON, &p.Settings)
	return &p, nil
}

func scanProviders(rows *sql.Rows) ([]models.ProviderConfig, error) {
	var result []models.ProviderConfig
	for rows.Next() {
		var p models.ProviderConfig
		var ownerID, modelDefault, keyScope sql.NullString
		var configJSON, headersJSON, settingsJSON []byte
		err := rows.Scan(
			&p.ID, &p.Scope, &ownerID, &p.Name, &p.Provider, &p.Endpoint,
			&p.APIKeyEnc, &p.KeyNonce, &keyScope, &modelDefault,
			&configJSON, &headersJSON, &settingsJSON,
			&p.IsActive, &p.IsPrivate, st(&p.CreatedAt), st(&p.UpdatedAt),
		)
		if err != nil {
			return nil, err
		}
		p.OwnerID = NullableStringPtr(ownerID)
		p.ModelDefault = modelDefault.String
		p.KeyScope = keyScope.String
		json.Unmarshal(configJSON, &p.Config)
		json.Unmarshal(headersJSON, &p.Headers)
		json.Unmarshal(settingsJSON, &p.Settings)
		result = append(result, p)
	}
	return result, rows.Err()
}

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

func (s *ProviderStore) DeletePersonalByOwner(ctx context.Context, ownerID string) (int64, error) {
	result, err := DB.ExecContext(ctx,
		`DELETE FROM provider_configs WHERE scope = 'personal' AND owner_id = ?`, ownerID)
	if err != nil {
		return 0, err
	}
	return result.RowsAffected()
}

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

func (s *ProviderStore) ListAllForTeam(ctx context.Context, teamID string) ([]models.ProviderConfig, error) {
	rows, err := DB.QueryContext(ctx,
		fmt.Sprintf("SELECT %s FROM provider_configs WHERE scope = 'team' AND owner_id = ? ORDER BY name", providerCols),
		teamID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	return scanProviders(rows)
}

func (s *ProviderStore) DeleteByIDAndTeam(ctx context.Context, id, teamID string) (int64, error) {
	res, err := DB.ExecContext(ctx,
		`DELETE FROM provider_configs WHERE id = ? AND scope = 'team' AND owner_id = ?`,
		id, teamID)
	if err != nil {
		return 0, err
	}
	return res.RowsAffected()
}

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

func (s *ProviderStore) FindFirstForUser(ctx context.Context, userID string) (string, error) {
	var configID string
	err := DB.QueryRowContext(ctx, `
		SELECT id FROM provider_configs
		WHERE is_active = 1 AND (
			(scope = 'personal' AND owner_id = ?)
			OR scope = 'global'
		)
		ORDER BY scope ASC, created_at ASC
		LIMIT 1
	`, userID).Scan(&configID)
	return configID, err
}

func (s *ProviderStore) LoadAccessible(ctx context.Context, configID, userID string) (*models.ProviderConfig, error) {
	row := DB.QueryRowContext(ctx, fmt.Sprintf(`
		SELECT %s FROM provider_configs
		WHERE id = ? AND is_active = 1
		  AND (scope = 'global'
		       OR (scope = 'personal' AND owner_id = ?)
		       OR (scope = 'team' AND owner_id IN (SELECT team_id FROM team_members WHERE user_id = ?)))
	`, providerCols), configID, userID, userID)
	return scanProvider(row)
}
