package web

import (
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
	"gitea.dooplex.hu/admin/felhom-controller/internal/config"
	"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
	"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
	"gitea.dooplex.hu/admin/felhom-controller/internal/system"
)

// F3 (AUDIT-vacation-remote-ops-2026-07-20): the dashboard's "Utolsó mentés" row branches on
// .BackupStatus, a key dashboardHandler never set — so the {{if}} arm was unreachable and every box
// reported "Még nem futott" forever, even with dumps on disk and crossdrive_completed events in the
// hub. These tests drive the REAL handler through ServeHTTP so they bite on the handler wiring, not
// just on the template.

// dashDumpProvider lists one deployed stack on a known drive, so the backup manager's per-drive dump
// scan has somewhere to look. Everything else comes from blockProvider (async_restore_test.go).
type dashDumpProvider struct {
	blockProvider
	stack string
}

func (p *dashDumpProvider) ListDeployedStacks() []backup.StackSummary {
	return []backup.StackSummary{{Name: p.stack}}
}

// newDashboardServer builds a Server that renders the real dashboard through ServeHTTP, backed by a
// real backup.Manager. When dumpAt is non-zero a DB dump file is planted on the app's drive with
// that modtime — GetFullStatus then synthesizes LastDBDump from it, which is what the card must show.
func newDashboardServer(t *testing.T, dumpAt time.Time) *Server {
	t.Helper()
	lg := log.New(io.Discard, "", 0)
	dir := t.TempDir()

	cfg := &config.Config{}
	cfg.Customer.ID = "test-customer"
	cfg.Customer.Name = "Teszt Ügyfél"
	cfg.Customer.Domain = "example.hu"
	cfg.Paths.StacksDir = filepath.Join(dir, "stacks")
	cfg.Paths.DataDir = filepath.Join(dir, "data")
	cfg.Paths.SystemDataPath = filepath.Join(dir, "system")
	cfg.Stacks.ComposeCommand = "docker compose" // skip detection (not needed for page renders)
	cfg.Backup.Enabled = true                    // the card only renders when backup is enabled

	sett, err := settings.Load(filepath.Join(dir, "settings.json"), lg)
	if err != nil {
		t.Fatalf("settings: %v", err)
	}
	stackMgr, err := stacks.NewManager(cfg, lg)
	if err != nil {
		t.Fatalf("stacks manager: %v", err)
	}

	drive := filepath.Join(dir, "drive")
	bm := backup.NewManager(cfg, sett, lg)
	bm.SetStackProvider(&dashDumpProvider{blockProvider{hdd: drive}, "bookstack"})

	if !dumpAt.IsZero() {
		dumpDir := backup.AppDBDumpPath(backup.NamespaceRoot(drive, true), "bookstack")
		if err := os.MkdirAll(dumpDir, 0o755); err != nil {
			t.Fatal(err)
		}
		f := filepath.Join(dumpDir, "bookstack-mariadb.sql")
		body := "-- MySQL dump\n-- Server version 12.3\n" + strings.Repeat("-- padding to clear the min-size validator\n", 4)
		if err := os.WriteFile(f, []byte(body), 0o644); err != nil {
			t.Fatal(err)
		}
		if err := os.Chtimes(f, dumpAt, dumpAt); err != nil {
			t.Fatal(err)
		}
	}
	bm.RefreshCache(time.Now()) // populates the cache the card reads (and the dump-file scan)

	s := &Server{cfg: cfg, settings: sett, stackMgr: stackMgr, backupMgr: bm, logger: lg, version: "test"}
	s.loadTemplates()
	return s
}

// backupCard returns just the dashboard's backup card, so an assertion cannot accidentally match
// the same words elsewhere on the page.
func backupCard(t *testing.T, body string) string {
	t.Helper()
	i := strings.Index(body, `class="backup-status-card"`)
	if i < 0 {
		t.Fatalf("no backup card on the dashboard (BackupEnabled false?)")
	}
	rest := body[i:]
	if j := strings.Index(rest, "</div>\n</div>"); j > 0 {
		return rest[:j]
	}
	return rest
}

// Scenario B — a real dump on disk ⇒ the card shows ITS timestamp, never "Még nem futott".
// COMPANION red-proof: delete `data["BackupStatus"] = fullStatus.LastDBDump` from dashboardHandler
// → the card falls back to "Még nem futott" and this test FAILS. Run → fail → restore (in REPORT).
func TestDashboardBackupCard_ShowsLastRun(t *testing.T) {
	dumpAt := time.Date(2026, 7, 19, 3, 30, 0, 0, time.Local)
	s := newDashboardServer(t, dumpAt)

	rec := getPage(t, s, "/dashboard") // v0.170.0: the Vezérlőpult moved from "/" to "/dashboard"
	if rec.Code != 200 {
		t.Fatalf("GET /dashboard = %d: %s", rec.Code, rec.Body.String())
	}
	card := backupCard(t, rec.Body.String())

	want := dumpAt.Format("2006-01-02 15:04")
	if !strings.Contains(card, want) {
		t.Errorf("card must show the real last run %q, got:\n%s", want, card)
	}
	if strings.Contains(card, "Még nem futott") {
		t.Errorf("a box WITH a dump must not claim it never ran, got:\n%s", card)
	}
}

// Scenario C — no dump anywhere ⇒ the fresh-box branch still renders honestly (nil passes through;
// the fix must not fabricate a zero timestamp like "0001-01-01 00:00").
func TestDashboardBackupCard_FreshBoxStaysHonest(t *testing.T) {
	s := newDashboardServer(t, time.Time{})

	rec := getPage(t, s, "/dashboard") // v0.170.0: the Vezérlőpult moved from "/" to "/dashboard"
	if rec.Code != 200 {
		t.Fatalf("GET /dashboard = %d: %s", rec.Code, rec.Body.String())
	}
	card := backupCard(t, rec.Body.String())

	if !strings.Contains(card, "Még nem futott") {
		t.Errorf("a box with NO dump must say so, got:\n%s", card)
	}
	if strings.Contains(card, "0001-01-01") {
		t.Errorf("nil dump must not render a zero-value timestamp, got:\n%s", card)
	}
}

// Scenario D — a FAILED dump renders "Sikertelen", not a timestamp and not "Még nem futott".
// The failure flag only exists in the manager's in-memory run state (unreachable from this package
// without shelling out to docker), so this one asserts the template branch directly.
func TestDashboardBackupCard_FailedRunShowsSikertelen(t *testing.T) {
	data := map[string]interface{}{
		"Page": "dashboard", "Title": "Vezérlőpult",
		"MissingStorage":  map[string]string{},
		"NetworkWarnings": map[string]string{},
		"NetworkStubs":    map[string]string{},
		"Subdomains":      map[string]string{},
		"RunningCount":    0, "StoppedCount": 0, "TotalCount": 0,
		"SystemInfo":    system.SystemInfo{},
		"Domain":        "demo-felhom.eu",
		"BackupEnabled": true,
		"BackupStatus": &backup.DBDumpStatus{
			LastRun: time.Date(2026, 7, 19, 3, 30, 0, 0, time.Local),
			Success: false,
		},
	}
	card := backupCard(t, renderBackupPage(t, "dashboard", data))

	if !strings.Contains(card, "Sikertelen") {
		t.Errorf("a failed dump must render Sikertelen, got:\n%s", card)
	}
	if strings.Contains(card, "2026-07-19 03:30") {
		t.Errorf("a failed dump must NOT render as a successful timestamp, got:\n%s", card)
	}
	if strings.Contains(card, "Még nem futott") {
		t.Errorf("a failed dump is not 'never ran', got:\n%s", card)
	}
}
