package store

import (
	"database/sql"
	"errors"
	"fmt"
	"strings"

	"github.com/google/uuid"
)

// CreateSharedSecret inserts a new shared-secret row after validating its
// scope/app_id pairing and non-empty name. The caller is responsible for
// encrypting Value when Encrypted is set (mirroring workload_env) — the
// store treats Value as opaque bytes.
func (s *Store) CreateSharedSecret(sec SharedSecret) (SharedSecret, error) {
	if err := validateSharedSecret(&sec); err != nil {
		return SharedSecret{}, err
	}
	now := Now()
	if sec.ID == "" {
		sec.ID = uuid.New().String()
	}
	sec.CreatedAt = now
	sec.UpdatedAt = now
	_, err := s.db.Exec(
		`INSERT INTO shared_secrets
		   (id, name, value, encrypted, scope, app_id, description, enabled, created_at, updated_at)
		 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		sec.ID, sec.Name, sec.Value, BoolToInt(sec.Encrypted), sec.Scope, sec.AppID,
		sec.Description, BoolToInt(sec.Enabled), sec.CreatedAt, sec.UpdatedAt,
	)
	if err != nil {
		return SharedSecret{}, fmt.Errorf("insert shared secret: %w", translateSQLError(err))
	}
	return sec, nil
}

// ListSharedSecrets returns every shared secret, ordered by scope then
// name for stable UI rendering (globals first).
func (s *Store) ListSharedSecrets() ([]SharedSecret, error) {
	return s.querySharedSecrets(
		`SELECT id, name, value, encrypted, scope, app_id, description, enabled, created_at, updated_at
		 FROM shared_secrets
		 ORDER BY CASE scope WHEN 'global' THEN 0 ELSE 1 END, app_id, name`,
	)
}

// GetSharedSecret fetches one shared secret by id or returns ErrNotFound.
func (s *Store) GetSharedSecret(id string) (SharedSecret, error) {
	row := s.db.QueryRow(
		`SELECT id, name, value, encrypted, scope, app_id, description, enabled, created_at, updated_at
		 FROM shared_secrets WHERE id = ?`, id,
	)
	sec, err := scanSharedSecretRow(row)
	if errors.Is(err, sql.ErrNoRows) {
		return SharedSecret{}, fmt.Errorf("shared secret %s: %w", id, ErrNotFound)
	}
	if err != nil {
		return SharedSecret{}, fmt.Errorf("query shared secret: %w", err)
	}
	return sec, nil
}

// UpdateSharedSecret overwrites the editable columns of a shared-secret
// row. id is immutable; name/value/encrypted/scope/app_id/description/
// enabled are overwritten wholesale (the API layer is responsible for
// merging partial PATCH input onto the existing row first).
func (s *Store) UpdateSharedSecret(sec SharedSecret) (SharedSecret, error) {
	if sec.ID == "" {
		return SharedSecret{}, fmt.Errorf("shared secret: id is required for update")
	}
	if err := validateSharedSecret(&sec); err != nil {
		return SharedSecret{}, err
	}
	sec.UpdatedAt = Now()
	res, err := s.db.Exec(
		`UPDATE shared_secrets
		    SET name = ?, value = ?, encrypted = ?, scope = ?, app_id = ?,
		        description = ?, enabled = ?, updated_at = ?
		  WHERE id = ?`,
		sec.Name, sec.Value, BoolToInt(sec.Encrypted), sec.Scope, sec.AppID,
		sec.Description, BoolToInt(sec.Enabled), sec.UpdatedAt, sec.ID,
	)
	if err != nil {
		return SharedSecret{}, fmt.Errorf("update shared secret: %w", translateSQLError(err))
	}
	n, _ := res.RowsAffected()
	if n == 0 {
		return SharedSecret{}, fmt.Errorf("shared secret %s: %w", sec.ID, ErrNotFound)
	}
	return s.GetSharedSecret(sec.ID)
}

// DeleteSharedSecret removes a shared secret by id, returning ErrNotFound
// when no row matched.
func (s *Store) DeleteSharedSecret(id string) error {
	res, err := s.db.Exec(`DELETE FROM shared_secrets WHERE id = ?`, id)
	if err != nil {
		return fmt.Errorf("delete shared secret: %w", err)
	}
	n, _ := res.RowsAffected()
	if n == 0 {
		return fmt.Errorf("shared secret %s: %w", id, ErrNotFound)
	}
	return nil
}

// ListApplicableSharedSecrets returns ENABLED secrets that apply to a
// workload in the given app: all global secrets plus the app's own.
// Ordered global-first so callers can overlay app on top of global.
func (s *Store) ListApplicableSharedSecrets(appID string) ([]SharedSecret, error) {
	return s.querySharedSecrets(
		`SELECT id, name, value, encrypted, scope, app_id, description, enabled, created_at, updated_at
		 FROM shared_secrets
		 WHERE enabled = 1 AND (scope = 'global' OR (scope = 'app' AND app_id = ?))
		 ORDER BY CASE scope WHEN 'global' THEN 0 ELSE 1 END, name`,
		appID,
	)
}

func (s *Store) querySharedSecrets(query string, args ...any) ([]SharedSecret, error) {
	rows, err := s.db.Query(query, args...)
	if err != nil {
		return nil, fmt.Errorf("query shared secrets: %w", err)
	}
	defer rows.Close()
	out := []SharedSecret{}
	for rows.Next() {
		sec, err := scanSharedSecretRows(rows)
		if err != nil {
			return nil, err
		}
		out = append(out, sec)
	}
	return out, rows.Err()
}

func scanSharedSecretRows(rows *sql.Rows) (SharedSecret, error) {
	var sec SharedSecret
	var enc, enabled int
	if err := rows.Scan(
		&sec.ID, &sec.Name, &sec.Value, &enc, &sec.Scope, &sec.AppID,
		&sec.Description, &enabled, &sec.CreatedAt, &sec.UpdatedAt,
	); err != nil {
		return SharedSecret{}, fmt.Errorf("scan shared secret: %w", err)
	}
	sec.Encrypted = enc != 0
	sec.Enabled = enabled != 0
	return sec, nil
}

func scanSharedSecretRow(row *sql.Row) (SharedSecret, error) {
	var sec SharedSecret
	var enc, enabled int
	if err := row.Scan(
		&sec.ID, &sec.Name, &sec.Value, &enc, &sec.Scope, &sec.AppID,
		&sec.Description, &enabled, &sec.CreatedAt, &sec.UpdatedAt,
	); err != nil {
		return SharedSecret{}, err
	}
	sec.Encrypted = enc != 0
	sec.Enabled = enabled != 0
	return sec, nil
}

// validateSharedSecret enforces the per-row invariants: a non-empty name,
// a valid scope, and a coherent scope/app_id pairing. When scope==app an
// app_id is required; when scope==global the app_id is forced blank so the
// unique index (scope, app_id, name) stays consistent for globals.
func validateSharedSecret(sec *SharedSecret) error {
	if strings.TrimSpace(sec.Name) == "" {
		return fmt.Errorf("shared secret: name is required")
	}
	switch sec.Scope {
	case SharedSecretScopeGlobal:
		sec.AppID = ""
	case SharedSecretScopeApp:
		if strings.TrimSpace(sec.AppID) == "" {
			return fmt.Errorf("shared secret: app_id is required when scope is app")
		}
	default:
		return fmt.Errorf("shared secret: invalid scope %q", sec.Scope)
	}
	return nil
}
