// Copyright (C) NHR@FAU, University Erlangen-Nuremberg.
// All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package archive

import (
	"encoding/json"
	"os"
	"testing"

	"github.com/ClusterCockpit/cc-lib/v2/schema"
)

func TestSqliteInitEmptyPath(t *testing.T) {
	var sa SqliteArchive
	_, err := sa.Init(json.RawMessage(`{"kind":"sqlite"}`))
	if err == nil {
		t.Fatal("expected error for empty database path")
	}
}

func TestSqliteInitInvalidConfig(t *testing.T) {
	var sa SqliteArchive
	_, err := sa.Init(json.RawMessage(`"db-path":"/tmp/test.db"`))
	if err == nil {
		t.Fatal("expected error for invalid config")
	}
}

func TestSqliteInit(t *testing.T) {
	tmpfile := t.TempDir() + "/test.db"
	defer os.Remove(tmpfile)

	var sa SqliteArchive
	version, err := sa.Init(json.RawMessage(`{"db-path":"` + tmpfile + `"}`))
	if err != nil {
		t.Fatalf("init failed: %v", err)
	}
	if version != Version {
		t.Errorf("expected version %d, got %d", Version, version)
	}
	if sa.db == nil {
		t.Fatal("database not initialized")
	}
	sa.db.Close()
}

func TestSqliteStoreAndLoadJobMeta(t *testing.T) {
	tmpfile := t.TempDir() + "/test.db"
	defer os.Remove(tmpfile)

	var sa SqliteArchive
	_, err := sa.Init(json.RawMessage(`{"db-path":"` + tmpfile + `"}`))
	if err != nil {
		t.Fatalf("init failed: %v", err)
	}
	defer sa.db.Close()

	job := &schema.Job{
		JobID:     12345,
		Cluster:   "test-cluster",
		StartTime: 1234567890,
		NumNodes:  1,
		Resources: []*schema.Resource{{Hostname: "node001"}},
	}

	// Store job metadata
	if err := sa.StoreJobMeta(job); err != nil {
		t.Fatalf("store failed: %v", err)
	}

	// Check if exists
	if !sa.Exists(job) {
		t.Fatal("job should exist")
	}

	// Load job metadata
	loaded, err := sa.LoadJobMeta(job)
	if err != nil {
		t.Fatalf("load failed: %v", err)
	}

	if loaded.JobID != job.JobID {
		t.Errorf("expected JobID %d, got %d", job.JobID, loaded.JobID)
	}
	if loaded.Cluster != job.Cluster {
		t.Errorf("expected Cluster %s, got %s", job.Cluster, loaded.Cluster)
	}
	if loaded.StartTime != job.StartTime {
		t.Errorf("expected StartTime %d, got %d", job.StartTime, loaded.StartTime)
	}
}

func TestSqliteImportJob(t *testing.T) {
	tmpfile := t.TempDir() + "/test.db"
	defer os.Remove(tmpfile)

	var sa SqliteArchive
	_, err := sa.Init(json.RawMessage(`{"db-path":"` + tmpfile + `"}`))
	if err != nil {
		t.Fatalf("init failed: %v", err)
	}
	defer sa.db.Close()

	// For now, skip complex JobData testing
	// Just test that ImportJob accepts the parameters
	// Full integration testing would require actual job data files
	t.Log("ImportJob interface verified (full data test requires integration)")
}

func TestSqliteGetClusters(t *testing.T) {
	tmpfile := t.TempDir() + "/test.db"
	defer os.Remove(tmpfile)

	var sa SqliteArchive
	_, err := sa.Init(json.RawMessage(`{"db-path":"` + tmpfile + `"}`))
	if err != nil {
		t.Fatalf("init failed: %v", err)
	}
	defer sa.db.Close()

	// Add jobs from different clusters
	job1 := &schema.Job{
		JobID:     1,
		Cluster:   "cluster-a",
		StartTime: 1000,
		NumNodes:  1,
		Resources: []*schema.Resource{{Hostname: "node001"}},
	}
	job2 := &schema.Job{
		JobID:     2,
		Cluster:   "cluster-b",
		StartTime: 2000,
		NumNodes:  1,
		Resources: []*schema.Resource{{Hostname: "node002"}},
	}

	sa.StoreJobMeta(job1)
	sa.StoreJobMeta(job2)

	// Reinitialize to refresh cluster list
	sa.db.Close()
	_, err = sa.Init(json.RawMessage(`{"db-path":"` + tmpfile + `"}`))
	if err != nil {
		t.Fatalf("reinit failed: %v", err)
	}
	defer sa.db.Close()

	clusters := sa.GetClusters()
	if len(clusters) != 2 {
		t.Errorf("expected 2 clusters, got %d", len(clusters))
	}
}

func TestSqliteCleanUp(t *testing.T) {
	tmpfile := t.TempDir() + "/test.db"
	defer os.Remove(tmpfile)

	var sa SqliteArchive
	_, err := sa.Init(json.RawMessage(`{"db-path":"` + tmpfile + `"}`))
	if err != nil {
		t.Fatalf("init failed: %v", err)
	}
	defer sa.db.Close()

	job := &schema.Job{
		JobID:     999,
		Cluster:   "test",
		StartTime: 5000,
		NumNodes:  1,
		Resources: []*schema.Resource{{Hostname: "node001"}},
	}

	sa.StoreJobMeta(job)

	// Verify exists
	if !sa.Exists(job) {
		t.Fatal("job should exist")
	}

	// Clean up
	sa.CleanUp([]*schema.Job{job})

	// Verify deleted
	if sa.Exists(job) {
		t.Fatal("job should not exist after cleanup")
	}
}

func TestSqliteClean(t *testing.T) {
	tmpfile := t.TempDir() + "/test.db"
	defer os.Remove(tmpfile)

	var sa SqliteArchive
	_, err := sa.Init(json.RawMessage(`{"db-path":"` + tmpfile + `"}`))
	if err != nil {
		t.Fatalf("init failed: %v", err)
	}
	defer sa.db.Close()

	// Add jobs with different start times
	oldJob := &schema.Job{
		JobID:     1,
		Cluster:   "test",
		StartTime: 1000,
		NumNodes:  1,
		Resources: []*schema.Resource{{Hostname: "node001"}},
	}
	newJob := &schema.Job{
		JobID:     2,
		Cluster:   "test",
		StartTime: 9000,
		NumNodes:  1,
		Resources: []*schema.Resource{{Hostname: "node002"}},
	}

	sa.StoreJobMeta(oldJob)
	sa.StoreJobMeta(newJob)

	// Clean jobs before 5000
	sa.Clean(5000, 0)

	// Old job should be deleted
	if sa.Exists(oldJob) {
		t.Error("old job should be deleted")
	}

	// New job should still exist
	if !sa.Exists(newJob) {
		t.Error("new job should still exist")
	}
}

func TestSqliteIter(t *testing.T) {
	tmpfile := t.TempDir() + "/test.db"
	defer os.Remove(tmpfile)

	var sa SqliteArchive
	_, err := sa.Init(json.RawMessage(`{"db-path":"` + tmpfile + `"}`))
	if err != nil {
		t.Fatalf("init failed: %v", err)
	}
	defer sa.db.Close()

	// Add multiple jobs
	for i := 1; i <= 3; i++ {
		job := &schema.Job{
			JobID:     int64(i),
			Cluster:   "test",
			StartTime: int64(i * 1000),
			NumNodes:  1,
			Resources: []*schema.Resource{{Hostname: "node001"}},
		}
		sa.StoreJobMeta(job)
	}

	// Iterate
	count := 0
	for container := range sa.Iter(false) {
		if container.Meta == nil {
			t.Error("expected non-nil meta")
		}
		count++
	}

	if count != 3 {
		t.Errorf("expected 3 jobs, got %d", count)
	}
}

func TestSqliteCompress(t *testing.T) {
	// Compression test requires actual job data
	// For now just verify the method exists and doesn't panic
	tmpfile := t.TempDir() + "/test.db"
	defer os.Remove(tmpfile)

	var sa SqliteArchive
	_, err := sa.Init(json.RawMessage(`{"db-path":"` + tmpfile + `"}`))
	if err != nil {
		t.Fatalf("init failed: %v", err)
	}
	defer sa.db.Close()

	job := &schema.Job{
		JobID:     777,
		Cluster:   "test",
		StartTime: 7777,
		NumNodes:  1,
		Resources: []*schema.Resource{{Hostname: "node001"}},
	}

	sa.StoreJobMeta(job)

	// Compress should not panic even with missing data
	sa.Compress([]*schema.Job{job})

	t.Log("Compression method verified")
}

func TestSqliteConfigParsing(t *testing.T) {
	rawConfig := json.RawMessage(`{"db-path": "/tmp/test.db"}`)

	var cfg SqliteArchiveConfig
	err := json.Unmarshal(rawConfig, &cfg)
	if err != nil {
		t.Fatalf("failed to parse config: %v", err)
	}

	if cfg.DBPath != "/tmp/test.db" {
		t.Errorf("expected dbPath '/tmp/test.db', got '%s'", cfg.DBPath)
	}
}

func TestSqliteIterChunking(t *testing.T) {
	tmpfile := t.TempDir() + "/test.db"
	defer os.Remove(tmpfile)

	var sa SqliteArchive
	_, err := sa.Init(json.RawMessage(`{"db-path":"` + tmpfile + `"}`))
	if err != nil {
		t.Fatalf("init failed: %v", err)
	}
	defer sa.db.Close()

	const totalJobs = 2500
	for i := 1; i <= totalJobs; i++ {
		job := &schema.Job{
			JobID:     int64(i),
			Cluster:   "test",
			StartTime: int64(i * 1000),
			NumNodes:  1,
			Resources: []*schema.Resource{{Hostname: "node001"}},
		}
		if err := sa.StoreJobMeta(job); err != nil {
			t.Fatalf("store failed: %v", err)
		}
	}

	t.Run("IterWithoutData", func(t *testing.T) {
		count := 0
		for container := range sa.Iter(false) {
			if container.Meta == nil {
				t.Error("expected non-nil meta")
			}
			if container.Data != nil {
				t.Error("expected nil data when loadMetricData is false")
			}
			count++
		}
		if count != totalJobs {
			t.Errorf("expected %d jobs, got %d", totalJobs, count)
		}
	})

	t.Run("IterWithData", func(t *testing.T) {
		count := 0
		for container := range sa.Iter(true) {
			if container.Meta == nil {
				t.Error("expected non-nil meta")
			}
			count++
		}
		if count != totalJobs {
			t.Errorf("expected %d jobs, got %d", totalJobs, count)
		}
	})
}
