package simulation

import (
	"context"
	"database/sql"
	"path/filepath"
	"testing"
	"testing/fstest"
	"time"

	"github.com/rs/zerolog"

	dbpkg "github.com/operator/command-center/internal/db"
)

func openSimDB(t *testing.T) *sql.DB {
	t.Helper()
	dbPath := filepath.Join(t.TempDir(), "test.db")
	db, err := dbpkg.OpenSQLite(dbPath)
	if err != nil {
		t.Fatalf("OpenSQLite: %v", err)
	}
	t.Cleanup(func() { _ = db.Close() })
	migs, _ := dbpkg.LoadMigrations(fstest.MapFS{
		"002_phase1.sql": {Data: []byte(`
			CREATE TABLE trackers (id TEXT PRIMARY KEY, name TEXT, type TEXT, base_url TEXT, config_json TEXT, enabled INTEGER, scrape_interval_seconds INTEGER, scrape_jitter_seconds INTEGER, use_byparr INTEGER, created_at INTEGER, updated_at INTEGER);
			CREATE TABLE ratio_snapshots (id INTEGER PRIMARY KEY AUTOINCREMENT, tracker_id TEXT NOT NULL, timestamp INTEGER NOT NULL, simulation_id INTEGER, real_uploaded_bytes INTEGER, real_downloaded_bytes INTEGER, real_ratio REAL, displayed_uploaded_bytes INTEGER, displayed_downloaded_bytes INTEGER, displayed_ratio REAL, bonus_points INTEGER, unsat_count INTEGER, unsat_limit INTEGER, class_or_rank TEXT, raw_json TEXT);
			CREATE TABLE torrents (info_hash TEXT PRIMARY KEY, name TEXT NOT NULL, size_bytes INTEGER NOT NULL, category TEXT, tags TEXT, first_seen_at INTEGER NOT NULL, last_seen_at INTEGER NOT NULL, source_filter_id TEXT, source_tracker_id TEXT, cross_seed_origin_hash TEXT, deleted_at INTEGER);
			CREATE TABLE torrent_trackers (id INTEGER PRIMARY KEY AUTOINCREMENT, info_hash TEXT NOT NULL, tracker_id TEXT NOT NULL, seed_time_required_seconds INTEGER, seed_time_accumulated_seconds INTEGER DEFAULT 0, h_and_r_risk_at INTEGER, UNIQUE(info_hash, tracker_id));
		`)},
		"004_phase3.sql": {Data: []byte(`
			CREATE TABLE torrent_clients (id TEXT PRIMARY KEY, name TEXT, type TEXT, base_url TEXT, proxy_via_qui INTEGER, config_json TEXT, enabled INTEGER, poll_interval_seconds INTEGER, created_at INTEGER, updated_at INTEGER);
			CREATE TABLE torrent_snapshots (id INTEGER PRIMARY KEY AUTOINCREMENT, info_hash TEXT NOT NULL, client_id TEXT NOT NULL, timestamp INTEGER NOT NULL, simulation_id INTEGER, uploaded_bytes INTEGER, downloaded_bytes INTEGER, state TEXT, ratio REAL, seeders INTEGER, leechers INTEGER, upload_speed_bps INTEGER, download_speed_bps INTEGER);
		`)},
		"006_phase5.sql": {Data: []byte(`
			CREATE TABLE automation_tools (id TEXT PRIMARY KEY, name TEXT, type TEXT, base_url TEXT, config_json TEXT, webhook_token TEXT, enabled INTEGER, poll_interval_seconds INTEGER, created_at INTEGER, updated_at INTEGER);
			CREATE TABLE filter_performance (id INTEGER PRIMARY KEY AUTOINCREMENT, automation_tool_id TEXT NOT NULL, filter_external_id TEXT NOT NULL, filter_name TEXT NOT NULL, variant_id TEXT, info_hash TEXT, release_name TEXT, grabbed_at INTEGER NOT NULL, final_uploaded_bytes INTEGER, final_ratio REAL, last_measured_at INTEGER);
		`)},
		"009_phase8.sql": {Data: []byte(`
			CREATE TABLE simulation_runs (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, annotation TEXT, proposed_config_json TEXT NOT NULL, window_start INTEGER NOT NULL, window_end INTEGER NOT NULL, started_at INTEGER NOT NULL, completed_at INTEGER, status TEXT NOT NULL, summary_json TEXT, promoted_at INTEGER);
		`)},
	})
	if _, err := dbpkg.Apply(db, migs); err != nil {
		t.Fatalf("Apply: %v", err)
	}
	return db
}

func TestCreateAndGet(t *testing.T) {
	db := openSimDB(t)
	e := NewEngine(db, zerolog.Nop())
	now := time.Now().Unix()
	id, err := e.Create(context.Background(), CreateParams{
		Name:           "Tighten filter X",
		ProposedConfig: map[string]any{"dead_swarm_threshold_hours": 96.0},
		WindowStart:    now - 30*86400,
		WindowEnd:      now,
	})
	if err != nil {
		t.Fatalf("Create: %v", err)
	}
	r, err := e.Get(context.Background(), id)
	if err != nil {
		t.Fatalf("Get: %v", err)
	}
	if r.Status != "pending" {
		t.Errorf("status: %q", r.Status)
	}
}

func TestRunCompletes(t *testing.T) {
	db := openSimDB(t)
	e := NewEngine(db, zerolog.Nop())
	now := time.Now().Unix()
	id, _ := e.Create(context.Background(), CreateParams{
		Name:           "test",
		ProposedConfig: map[string]any{},
		WindowStart:    now - 30*86400,
		WindowEnd:      now,
	})
	if err := e.Run(context.Background(), id); err != nil {
		t.Fatalf("Run: %v", err)
	}
	r, _ := e.Get(context.Background(), id)
	if r.Status != "completed" {
		t.Errorf("status: %q", r.Status)
	}
	if r.SummaryJSON == "" {
		t.Error("summary missing")
	}
}

func TestRunRejectsCompletedTwice(t *testing.T) {
	db := openSimDB(t)
	e := NewEngine(db, zerolog.Nop())
	now := time.Now().Unix()
	id, _ := e.Create(context.Background(), CreateParams{
		Name:           "test",
		ProposedConfig: map[string]any{},
		WindowStart:    now - 30*86400,
		WindowEnd:      now,
	})
	_ = e.Run(context.Background(), id)
	if err := e.Run(context.Background(), id); err == nil {
		t.Error("expected error running already-completed simulation")
	}
}

func TestDeleteAndPromote(t *testing.T) {
	db := openSimDB(t)
	e := NewEngine(db, zerolog.Nop())
	now := time.Now().Unix()
	id, _ := e.Create(context.Background(), CreateParams{
		Name:           "x",
		ProposedConfig: map[string]any{},
		WindowStart:    now - 10,
		WindowEnd:      now,
	})
	if err := e.Promote(context.Background(), id); err != nil {
		t.Fatalf("Promote: %v", err)
	}
	r, _ := e.Get(context.Background(), id)
	if r.PromotedAt == nil {
		t.Error("PromotedAt not set")
	}
	if err := e.Delete(context.Background(), id); err != nil {
		t.Fatalf("Delete: %v", err)
	}
	if _, err := e.Get(context.Background(), id); err == nil {
		t.Error("expected error after delete")
	}
}
