package catalog

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"testing"
	"time"
)

func TestSQLiteCatalog(t *testing.T) {
	// Create temp directory for test database
	tmpDir, err := os.MkdirTemp("", "catalog_test")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDir)

	dbPath := filepath.Join(tmpDir, "test_catalog.db")

	// Test creation
	cat, err := NewSQLiteCatalog(dbPath)
	if err != nil {
		t.Fatalf("Failed to create catalog: %v", err)
	}
	defer cat.Close()

	ctx := context.Background()

	// Test Add
	entry := &Entry{
		Database:     "testdb",
		DatabaseType: "postgresql",
		Host:         "localhost",
		Port:         5432,
		BackupPath:   "/backups/testdb_20240115.dump.gz",
		BackupType:   "full",
		SizeBytes:    1024 * 1024 * 100, // 100 MB
		SHA256:       "abc123def456",
		Compression:  "gzip",
		Encrypted:    false,
		CreatedAt:    time.Now().Add(-24 * time.Hour),
		Duration:     45.5,
		Status:       StatusCompleted,
	}

	err = cat.Add(ctx, entry)
	if err != nil {
		t.Fatalf("Failed to add entry: %v", err)
	}

	if entry.ID == 0 {
		t.Error("Expected entry ID to be set after Add")
	}

	// Test Get
	retrieved, err := cat.Get(ctx, entry.ID)
	if err != nil {
		t.Fatalf("Failed to get entry: %v", err)
	}

	if retrieved == nil {
		t.Fatal("Expected to retrieve entry, got nil")
	}

	if retrieved.Database != "testdb" {
		t.Errorf("Expected database 'testdb', got '%s'", retrieved.Database)
	}

	if retrieved.SizeBytes != entry.SizeBytes {
		t.Errorf("Expected size %d, got %d", entry.SizeBytes, retrieved.SizeBytes)
	}

	// Test GetByPath
	byPath, err := cat.GetByPath(ctx, entry.BackupPath)
	if err != nil {
		t.Fatalf("Failed to get by path: %v", err)
	}

	if byPath == nil || byPath.ID != entry.ID {
		t.Error("GetByPath returned wrong entry")
	}

	// Test List
	entries, err := cat.List(ctx, "testdb", 10)
	if err != nil {
		t.Fatalf("Failed to list entries: %v", err)
	}

	if len(entries) != 1 {
		t.Errorf("Expected 1 entry, got %d", len(entries))
	}

	// Test ListDatabases
	databases, err := cat.ListDatabases(ctx)
	if err != nil {
		t.Fatalf("Failed to list databases: %v", err)
	}

	if len(databases) != 1 || databases[0] != "testdb" {
		t.Errorf("Expected ['testdb'], got %v", databases)
	}

	// Test Stats
	stats, err := cat.Stats(ctx)
	if err != nil {
		t.Fatalf("Failed to get stats: %v", err)
	}

	if stats.TotalBackups != 1 {
		t.Errorf("Expected 1 total backup, got %d", stats.TotalBackups)
	}

	if stats.TotalSize != entry.SizeBytes {
		t.Errorf("Expected size %d, got %d", entry.SizeBytes, stats.TotalSize)
	}

	// Test MarkVerified
	err = cat.MarkVerified(ctx, entry.ID, true)
	if err != nil {
		t.Fatalf("Failed to mark verified: %v", err)
	}

	verified, _ := cat.Get(ctx, entry.ID)
	if verified.VerifiedAt == nil {
		t.Error("Expected VerifiedAt to be set")
	}
	if verified.VerifyValid == nil || !*verified.VerifyValid {
		t.Error("Expected VerifyValid to be true")
	}

	// Test Update
	entry.SizeBytes = 200 * 1024 * 1024 // 200 MB
	err = cat.Update(ctx, entry)
	if err != nil {
		t.Fatalf("Failed to update entry: %v", err)
	}

	updated, _ := cat.Get(ctx, entry.ID)
	if updated.SizeBytes != entry.SizeBytes {
		t.Errorf("Update failed: expected size %d, got %d", entry.SizeBytes, updated.SizeBytes)
	}

	// Test Search with filters
	query := &SearchQuery{
		Database:  "testdb",
		Limit:     10,
		OrderBy:   "created_at",
		OrderDesc: true,
	}

	results, err := cat.Search(ctx, query)
	if err != nil {
		t.Fatalf("Search failed: %v", err)
	}

	if len(results) != 1 {
		t.Errorf("Expected 1 result, got %d", len(results))
	}

	// Test Search with wildcards
	query.Database = "test*"
	results, err = cat.Search(ctx, query)
	if err != nil {
		t.Fatalf("Wildcard search failed: %v", err)
	}

	if len(results) != 1 {
		t.Errorf("Expected 1 result from wildcard search, got %d", len(results))
	}

	// Test Count
	count, err := cat.Count(ctx, &SearchQuery{Database: "testdb"})
	if err != nil {
		t.Fatalf("Count failed: %v", err)
	}

	if count != 1 {
		t.Errorf("Expected count 1, got %d", count)
	}

	// Test Delete
	err = cat.Delete(ctx, entry.ID)
	if err != nil {
		t.Fatalf("Failed to delete entry: %v", err)
	}

	deleted, _ := cat.Get(ctx, entry.ID)
	if deleted != nil {
		t.Error("Expected entry to be deleted")
	}
}

func TestGapDetection(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "catalog_gaps_test")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDir)

	dbPath := filepath.Join(tmpDir, "test_catalog.db")
	cat, err := NewSQLiteCatalog(dbPath)
	if err != nil {
		t.Fatalf("Failed to create catalog: %v", err)
	}
	defer cat.Close()

	ctx := context.Background()

	// Add backups with varying intervals
	now := time.Now()
	backups := []time.Time{
		now.Add(-7 * 24 * time.Hour), // 7 days ago
		now.Add(-6 * 24 * time.Hour), // 6 days ago (OK)
		now.Add(-5 * 24 * time.Hour), // 5 days ago (OK)
		// Missing 4 days ago - GAP
		now.Add(-3 * 24 * time.Hour), // 3 days ago
		now.Add(-2 * 24 * time.Hour), // 2 days ago (OK)
		// Missing 1 day ago and today - GAP to now
	}

	for i, ts := range backups {
		entry := &Entry{
			Database:     "gaptest",
			DatabaseType: "postgresql",
			BackupPath:   filepath.Join(tmpDir, fmt.Sprintf("backup_%d.dump", i)),
			BackupType:   "full",
			CreatedAt:    ts,
			Status:       StatusCompleted,
		}
		cat.Add(ctx, entry)
	}

	// Detect gaps with 24h expected interval
	config := &GapDetectionConfig{
		ExpectedInterval: 24 * time.Hour,
		Tolerance:        2 * time.Hour,
		RPOThreshold:     48 * time.Hour,
	}

	gaps, err := cat.DetectGaps(ctx, "gaptest", config)
	if err != nil {
		t.Fatalf("Gap detection failed: %v", err)
	}

	// Should detect at least 2 gaps:
	// 1. Between 5 days ago and 3 days ago (missing 4 days ago)
	// 2. Between 2 days ago and now (missing recent backups)
	if len(gaps) < 2 {
		t.Errorf("Expected at least 2 gaps, got %d", len(gaps))
	}

	// Check gap severities
	hasCritical := false
	for _, gap := range gaps {
		if gap.Severity == SeverityCritical {
			hasCritical = true
		}
		if gap.Duration < config.ExpectedInterval {
			t.Errorf("Gap duration %v is less than expected interval", gap.Duration)
		}
	}

	// The gap from 2 days ago to now should be critical (>48h)
	if !hasCritical {
		t.Log("Note: Expected at least one critical gap")
	}
}

func TestFormatSize(t *testing.T) {
	tests := []struct {
		bytes    int64
		expected string
	}{
		{0, "0 B"},
		{500, "500 B"},
		{1024, "1.0 KB"},
		{1024 * 1024, "1.0 MB"},
		{1024 * 1024 * 1024, "1.0 GB"},
		{1024 * 1024 * 1024 * 1024, "1.0 TB"},
	}

	for _, test := range tests {
		result := FormatSize(test.bytes)
		if result != test.expected {
			t.Errorf("FormatSize(%d) = %s, expected %s", test.bytes, result, test.expected)
		}
	}
}

func TestFormatDuration(t *testing.T) {
	tests := []struct {
		duration time.Duration
		expected string
	}{
		{30 * time.Second, "30s"},
		{90 * time.Second, "1m 30s"},
		{2 * time.Hour, "2h 0m"},
	}

	for _, test := range tests {
		result := FormatDuration(test.duration)
		if result != test.expected {
			t.Errorf("FormatDuration(%v) = %s, expected %s", test.duration, result, test.expected)
		}
	}
}
