// Package database provides checkpoint management for scan sessions.
// This file implements periodic checkpointing to enable resume of interrupted scans.
package database

import (
	"database/sql"
	"sync"
	"sync/atomic"
	"time"
)

// CheckpointConfig configures checkpoint behavior.
type CheckpointConfig struct {
	// Interval is the number of domains between checkpoints.
	// Default: 100
	Interval int
}

// DefaultCheckpointConfig returns sensible defaults.
func DefaultCheckpointConfig() CheckpointConfig {
	return CheckpointConfig{
		Interval: 100,
	}
}

// CheckpointManager handles session lifecycle and periodic progress checkpointing.
// It tracks completed domains in memory and flushes to database every N domains.
//
// Thread Safety:
// CheckpointManager is safe for concurrent use. Multiple goroutines can call
// RecordDomainComplete() simultaneously - internal mutex protects the pending
// domains buffer.
type CheckpointManager struct {
	db        *sql.DB
	sessionID int64
	cfg       CheckpointConfig

	// In-memory buffer of completed domains since last checkpoint
	pendingDomains []string
	mu             sync.Mutex

	// Counter for checkpoint timing
	completedCount atomic.Int64

	// Last checkpoint time for visual indicator
	lastCheckpoint atomic.Value // time.Time
}

// NewCheckpointManager creates a manager for the given session.
// Session must already exist in database (created via CreateSession).
func NewCheckpointManager(db *sql.DB, sessionID int64, cfg CheckpointConfig) *CheckpointManager {
	if cfg.Interval <= 0 {
		cfg.Interval = 100
	}

	cm := &CheckpointManager{
		db:             db,
		sessionID:      sessionID,
		cfg:            cfg,
		pendingDomains: make([]string, 0, cfg.Interval),
	}
	cm.lastCheckpoint.Store(time.Now())
	return cm
}

// RecordDomainComplete records that a domain scan has finished.
// Triggers checkpoint when interval reached.
// Returns true if a checkpoint was saved (for visual indicator).
//
// Thread-safe: may be called from multiple goroutines concurrently.
func (cm *CheckpointManager) RecordDomainComplete(domain string) bool {
	cm.mu.Lock()
	cm.pendingDomains = append(cm.pendingDomains, domain)
	count := cm.completedCount.Add(1)
	shouldCheckpoint := len(cm.pendingDomains) >= cm.cfg.Interval

	if shouldCheckpoint {
		// Copy pending domains and reset
		domains := make([]string, len(cm.pendingDomains))
		copy(domains, cm.pendingDomains)
		cm.pendingDomains = cm.pendingDomains[:0]
		cm.mu.Unlock()

		// Flush to database
		cm.flushCheckpoint(domains, int(count))
		return true
	}
	cm.mu.Unlock()
	return false
}

// flushCheckpoint writes pending domains to database and updates session progress.
func (cm *CheckpointManager) flushCheckpoint(domains []string, totalCompleted int) {
	// Begin transaction for atomic checkpoint
	tx, err := cm.db.Begin()
	if err != nil {
		// Log error but don't fail - data is still in memory
		return
	}
	defer tx.Rollback()

	// Insert completed domains
	stmt, err := tx.Prepare("INSERT OR IGNORE INTO session_progress (session_id, domain) VALUES (?, ?)")
	if err != nil {
		return
	}
	defer stmt.Close()

	for _, domain := range domains {
		stmt.Exec(cm.sessionID, domain)
	}

	// Update session progress count
	_, err = tx.Exec(
		"UPDATE sessions SET completed_targets = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?",
		totalCompleted, cm.sessionID,
	)
	if err != nil {
		return
	}

	tx.Commit()
	cm.lastCheckpoint.Store(time.Now())
}

// Flush forces a checkpoint of any pending domains.
// Call on scan completion or pause.
func (cm *CheckpointManager) Flush() {
	cm.mu.Lock()
	if len(cm.pendingDomains) == 0 {
		cm.mu.Unlock()
		return
	}

	domains := make([]string, len(cm.pendingDomains))
	copy(domains, cm.pendingDomains)
	cm.pendingDomains = cm.pendingDomains[:0]
	count := int(cm.completedCount.Load())
	cm.mu.Unlock()

	cm.flushCheckpoint(domains, count)
}

// Complete marks the session as complete and flushes any pending domains.
func (cm *CheckpointManager) Complete() error {
	cm.Flush()
	return MarkSessionComplete(cm.db, cm.sessionID)
}

// Pause marks the session as paused and flushes any pending domains.
func (cm *CheckpointManager) Pause() error {
	cm.Flush()

	// Mark session as paused
	if err := MarkSessionPaused(cm.db, cm.sessionID); err != nil {
		return err
	}

	// Force WAL checkpoint to ensure data is written to disk
	// This is critical for Ctrl+C shutdown scenarios
	_, err := cm.db.Exec("PRAGMA wal_checkpoint(TRUNCATE)")
	return err
}

// SessionID returns the session ID being managed.
func (cm *CheckpointManager) SessionID() int64 {
	return cm.sessionID
}

// LastCheckpointTime returns when the last checkpoint was saved.
// Used for visual indicator in TUI.
func (cm *CheckpointManager) LastCheckpointTime() time.Time {
	return cm.lastCheckpoint.Load().(time.Time)
}

// CompletedCount returns total domains completed in this session.
func (cm *CheckpointManager) CompletedCount() int64 {
	return cm.completedCount.Load()
}

// StartSession creates a new session and returns a CheckpointManager for it.
// sessionName is derived from input (domain or filename).
// totalTargets is the number of domains to scan (0 if unknown).
// targetsFile is the path to targets file (empty string for single domain scans).
func StartSession(db *sql.DB, sessionName string, totalTargets int, targetsFile string, cfg CheckpointConfig) (*CheckpointManager, *Session, error) {
	session, err := CreateSession(db, sessionName, totalTargets, targetsFile)
	if err != nil {
		return nil, nil, err
	}

	cm := NewCheckpointManager(db, session.ID, cfg)
	return cm, session, nil
}

// ResumeSession loads an existing session and returns a CheckpointManager.
// Returns completed domains map for filtering targets.
func ResumeSession(db *sql.DB, sessionID int64, cfg CheckpointConfig) (*CheckpointManager, *Session, map[string]bool, error) {
	session, err := GetSession(db, sessionID)
	if err != nil {
		return nil, nil, nil, err
	}

	completedDomains, err := GetCompletedDomains(db, sessionID)
	if err != nil {
		return nil, nil, nil, err
	}

	cm := NewCheckpointManager(db, sessionID, cfg)
	// Restore counter from session
	cm.completedCount.Store(int64(session.CompletedTargets))

	// Mark session as active again (was paused)
	db.Exec("UPDATE sessions SET status = 'active', updated_at = CURRENT_TIMESTAMP WHERE id = ?", sessionID)

	return cm, session, completedDomains, nil
}
