package imap

import (
	"context"
	"errors"
	"fmt"

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

// FolderState represents per-folder UID tracking for an IMAP account (PROJ-45).
// Each IMAP folder has its own independent UID sequence — tracking a single
// account-global last_uid (the legacy behaviour) silently skips folders whose
// highest UID falls below the global value and misses UIDVALIDITY resets.
type FolderState struct {
	AccountID   int64
	Folder      string
	LastUID     uint32
	UIDValidity uint32
}

// GetFolderState returns the saved per-folder UID tracking record.
// If no record exists yet (first sync of this folder), an empty
// FolderState{LastUID: 0, UIDValidity: 0} is returned and the caller
// MUST treat it as a request for a full folder resync.
func (s *Store) GetFolderState(ctx context.Context, accountID int64, folder string) (FolderState, error) {
	var st FolderState
	st.AccountID = accountID
	st.Folder = folder

	err := s.pool.QueryRow(ctx, `
		SELECT last_uid, uid_validity
		FROM imap_folder_state
		WHERE account_id = $1 AND folder = $2`,
		accountID, folder,
	).Scan(&st.LastUID, &st.UIDValidity)

	if errors.Is(err, pgx.ErrNoRows) {
		return st, nil
	}
	if err != nil {
		return st, fmt.Errorf("imap store: get folder state: %w", err)
	}
	return st, nil
}

// UpsertFolderState writes (or overwrites) the per-folder UID tracking record.
// Used after a successful folder sync to persist the new high-water mark.
func (s *Store) UpsertFolderState(ctx context.Context, st FolderState) error {
	_, err := s.pool.Exec(ctx, `
		INSERT INTO imap_folder_state (account_id, folder, last_uid, uid_validity, updated_at)
		VALUES ($1, $2, $3, $4, now())
		ON CONFLICT (account_id, folder) DO UPDATE
			SET last_uid     = EXCLUDED.last_uid,
			    uid_validity = EXCLUDED.uid_validity,
			    updated_at   = now()`,
		st.AccountID, st.Folder, st.LastUID, st.UIDValidity,
	)
	if err != nil {
		return fmt.Errorf("imap store: upsert folder state: %w", err)
	}
	return nil
}

// ResyncDecision describes what the scheduler should do for a folder given
// the stored state and the UIDVALIDITY reported by the server right after SELECT.
// Extracted as a pure function so the branching logic can be unit-tested
// without a live IMAP server.
type ResyncDecision struct {
	FullResync     bool   // true → ignore stored last_uid, fetch all UIDs
	UIDValidReset  bool   // true → write an audit-log "uidvalidity_reset" entry
	EffectiveStart uint32 // UID to filter on (0 means "all messages")
}

// DecideResync implements the per-folder sync gate (PROJ-45).
//
//   - server UIDVALIDITY == 0       → treat as "unknown", continue incrementally
//     with the stored last_uid (defensive; some servers misbehave).
//   - stored UIDVALIDITY == 0       → first ever sync of this folder → full sync,
//     but no "reset" event (nothing to reset from).
//   - stored != server (both != 0)  → mailbox rebuilt server-side → full resync
//     AND log a UIDVALIDITY-reset audit event.
//   - otherwise                     → incremental from stored last_uid.
func DecideResync(storedLastUID, storedUIDValidity, serverUIDValidity uint32) ResyncDecision {
	if serverUIDValidity == 0 {
		return ResyncDecision{
			FullResync:     storedLastUID == 0,
			EffectiveStart: storedLastUID,
		}
	}
	if storedUIDValidity == 0 {
		return ResyncDecision{FullResync: storedLastUID == 0, EffectiveStart: storedLastUID}
	}
	if storedUIDValidity != serverUIDValidity {
		return ResyncDecision{FullResync: true, UIDValidReset: true, EffectiveStart: 0}
	}
	return ResyncDecision{FullResync: storedLastUID == 0, EffectiveStart: storedLastUID}
}

// ListFolderStates returns all per-folder UID tracking records for an account.
// Currently used for diagnostics and potential admin UI.
func (s *Store) ListFolderStates(ctx context.Context, accountID int64) ([]FolderState, error) {
	rows, err := s.pool.Query(ctx, `
		SELECT account_id, folder, last_uid, uid_validity
		FROM imap_folder_state
		WHERE account_id = $1
		ORDER BY folder`, accountID)
	if err != nil {
		return nil, fmt.Errorf("imap store: list folder states: %w", err)
	}
	defer rows.Close()

	var states []FolderState
	for rows.Next() {
		var st FolderState
		if err := rows.Scan(&st.AccountID, &st.Folder, &st.LastUID, &st.UIDValidity); err != nil {
			return nil, fmt.Errorf("imap store: scan folder state: %w", err)
		}
		states = append(states, st)
	}
	return states, rows.Err()
}
