package db

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/jackc/pgx/v5"
)

// SteamCredentialRow is a single stored Steam account for SteamCMD
// non-anonymous login. v1 is single-operator, so most panels will have
// at most one row.
type SteamCredentialRow struct {
	ID                 int64
	Username           string
	PasswordCiphertext []byte // AES-GCM ciphertext of the password
	PasswordNonce      []byte // AES-GCM nonce (12 bytes)
	SentryFile         []byte // SSFN device-auth blob; NULL until first success
	LastUsedAt         *time.Time
	CreatedAt          time.Time
	UpdatedAt          time.Time
}

// GetSteamCredentialByUsername returns the stored credential for the given
// username or pgx.ErrNoRows if none exists.
func (db *DB) GetSteamCredentialByUsername(ctx context.Context, username string) (*SteamCredentialRow, error) {
	row := db.pool.QueryRow(ctx, `
		SELECT id, username, password_ciphertext, password_nonce, sentry_file, last_used_at, created_at, updated_at
		  FROM steam_credentials
		 WHERE username = $1`, username)
	var r SteamCredentialRow
	if err := row.Scan(&r.ID, &r.Username, &r.PasswordCiphertext, &r.PasswordNonce, &r.SentryFile, &r.LastUsedAt, &r.CreatedAt, &r.UpdatedAt); err != nil {
		return nil, err
	}
	return &r, nil
}

// GetAnySteamCredential returns the most-recently-used Steam credential, or
// pgx.ErrNoRows if none exist. Used when the caller doesn't care which
// account (single-operator panels typically have one).
func (db *DB) GetAnySteamCredential(ctx context.Context) (*SteamCredentialRow, error) {
	row := db.pool.QueryRow(ctx, `
		SELECT id, username, password_ciphertext, password_nonce, sentry_file, last_used_at, created_at, updated_at
		  FROM steam_credentials
		 ORDER BY COALESCE(last_used_at, created_at) DESC
		 LIMIT 1`)
	var r SteamCredentialRow
	if err := row.Scan(&r.ID, &r.Username, &r.PasswordCiphertext, &r.PasswordNonce, &r.SentryFile, &r.LastUsedAt, &r.CreatedAt, &r.UpdatedAt); err != nil {
		return nil, err
	}
	return &r, nil
}

// UpsertSteamCredential stores (or replaces) a Steam credential. Leaves
// sentry_file untouched on update — that's populated separately by
// UpdateSteamSentryFile once a SteamCMD login succeeds.
func (db *DB) UpsertSteamCredential(ctx context.Context, username string, cipher, nonce []byte) error {
	_, err := db.pool.Exec(ctx, `
		INSERT INTO steam_credentials (username, password_ciphertext, password_nonce, updated_at)
		     VALUES ($1, $2, $3, NOW())
		ON CONFLICT (username)
		  DO UPDATE SET password_ciphertext = EXCLUDED.password_ciphertext,
		                password_nonce      = EXCLUDED.password_nonce,
		                updated_at          = NOW()`,
		username, cipher, nonce)
	if err != nil {
		return fmt.Errorf("upsert steam credential: %w", err)
	}
	return nil
}

// UpdateSteamSentryFile stores the SSFN blob after a successful login so
// subsequent SteamCMD invocations can skip the Steam Guard 2FA prompt.
func (db *DB) UpdateSteamSentryFile(ctx context.Context, username string, sentry []byte) error {
	res, err := db.pool.Exec(ctx, `
		UPDATE steam_credentials
		   SET sentry_file = $2, last_used_at = NOW(), updated_at = NOW()
		 WHERE username = $1`,
		username, sentry)
	if err != nil {
		return fmt.Errorf("update sentry: %w", err)
	}
	if res.RowsAffected() == 0 {
		return errors.New("steam credential not found")
	}
	return nil
}

// TouchSteamCredential updates last_used_at without writing a new sentry.
func (db *DB) TouchSteamCredential(ctx context.Context, username string) error {
	_, err := db.pool.Exec(ctx, `UPDATE steam_credentials SET last_used_at = NOW() WHERE username = $1`, username)
	return err
}

// DeleteSteamCredential removes a cached credential (operator action).
func (db *DB) DeleteSteamCredential(ctx context.Context, username string) error {
	_, err := db.pool.Exec(ctx, `DELETE FROM steam_credentials WHERE username = $1`, username)
	return err
}

// ListSteamCredentialUsernames returns just the usernames (no password
// material) so the UI can show "Steam accounts configured: X" without
// touching the secrets.
func (db *DB) ListSteamCredentialUsernames(ctx context.Context) ([]string, error) {
	rows, err := db.pool.Query(ctx, `SELECT username FROM steam_credentials ORDER BY username`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var names []string
	for rows.Next() {
		var u string
		if err := rows.Scan(&u); err != nil {
			return nil, err
		}
		names = append(names, u)
	}
	return names, rows.Err()
}

// avoid unused-import removal
var _ = pgx.ErrNoRows
