package web

import (
	"io"
	"log"
	"net/http/httptest"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"testing"

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

func newOffboxWebServer(t *testing.T) (*Server, *settings.Settings, *backup.Manager) {
	t.Helper()
	tmp := t.TempDir()
	lg := log.New(io.Discard, "", 0)
	sett, err := settings.Load(filepath.Join(tmp, "settings.json"), lg)
	if err != nil {
		t.Fatal(err)
	}
	cfg := &config.Config{}
	cfg.Paths.DataDir = tmp
	m := backup.NewManager(cfg, sett, lg)
	return &Server{cfg: cfg, backupMgr: m, settings: sett, logger: lg}, sett, m
}

// The run handler refuses while escrow is pending, and confirm-escrow flips to escrowed + runnable.
func TestOffboxWeb_RunGatedUntilConfirm(t *testing.T) {
	s, sett, m := newOffboxWebServer(t)
	if err := m.WriteOffboxSecrets("KEYMATERIAL", "nas.local ssh-ed25519 HOSTKEY"); err != nil {
		t.Fatal(err)
	}
	if err := sett.SetOffboxTarget(&settings.OffboxTarget{
		Enabled: true, Host: "nas.local", Port: 22, User: "felhom", RepoPath: "/srv/repo", Schedule: "daily",
		EscrowState: "pending",
	}); err != nil {
		t.Fatal(err)
	}
	if !m.OffboxConfigured() {
		t.Fatal("target should be configured")
	}
	// run while pending → refused with the escrow-wait flash, no run launched
	w := httptest.NewRecorder()
	s.offboxRunHandler(w, httptest.NewRequest("POST", "/backup/offbox/run", nil))
	if loc := w.Header().Get("Location"); w.Code != 302 || !strings.Contains(loc, "let%C3%A9t") {
		t.Fatalf("pending run must redirect with the escrow-wait flash, got %d %q", w.Code, loc)
	}
	if m.OffboxRunnable() {
		t.Fatal("must not be runnable while pending")
	}
	// confirm-escrow → escrowed + runnable
	w2 := httptest.NewRecorder()
	s.offboxConfirmEscrowHandler(w2, httptest.NewRequest("POST", "/backup/offbox/confirm-escrow", nil))
	if w2.Code != 302 {
		t.Fatalf("confirm: got %d", w2.Code)
	}
	if got := sett.GetOffboxTarget().EscrowState; got != "escrowed" {
		t.Fatalf("confirm must set EscrowState=escrowed, got %q", got)
	}
	if !m.OffboxRunnable() {
		t.Fatal("must be runnable after confirm")
	}
}

// The inject endpoint pre-places a recovered password (DR seam).
func TestOffboxWeb_InjectPassword(t *testing.T) {
	s, _, _ := newOffboxWebServer(t)
	pwPath := filepath.Join(s.cfg.Paths.DataDir, "offbox", "repo_password")
	const pw = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
	form := url.Values{"password": {pw}}
	r := httptest.NewRequest("POST", "/backup/offbox/inject-password", strings.NewReader(form.Encode()))
	r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	w := httptest.NewRecorder()
	s.offboxInjectPasswordHandler(w, r)
	if w.Code != 302 {
		t.Fatalf("inject: got %d", w.Code)
	}
	got, err := os.ReadFile(pwPath)
	if err != nil || string(got) != pw {
		t.Fatalf("injected password not placed 0600 at offboxPwPath: err=%v", err)
	}
	// an invalid password is refused (error flash)
	bad := url.Values{"password": {"nope"}}
	rb := httptest.NewRequest("POST", "/backup/offbox/inject-password", strings.NewReader(bad.Encode()))
	rb.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	wb := httptest.NewRecorder()
	s.offboxInjectPasswordHandler(wb, rb)
	if loc := wb.Header().Get("Location"); !strings.Contains(loc, "flash_error") {
		t.Fatalf("invalid password must produce an error flash, got %q", loc)
	}
}
