package web

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

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

// R-75 Scenario E — the system share's delete refusal is SERVER-SIDE.
//
// Two independent checks, tested independently on purpose (the v0.70.1 lesson): a handler test that
// POSTs directly proves nothing about UI reachability, and a render gate proves nothing about
// enforcement. Both are required; neither substitutes for the other.

func serverWithImportShare(t *testing.T) *Server {
	t.Helper()
	s := testServer(t)
	// A real Manager so the handler's post-delete ReconcileSamba has a receiver. Sharing is left
	// DISABLED, so reconcileSambaAt early-returns and no docker call is made.
	s.cfg.Paths.SystemDataPath = "/mnt/sys_drive"
	s.cfg.Paths.StacksDir = t.TempDir()
	mgr, err := stacks.NewManager(s.cfg, s.logger)
	if err != nil {
		t.Fatal(err)
	}
	mgr.SetMigrationDeps(s.settings, func() bool { return false })
	s.stackMgr = mgr
	if err := s.settings.AddSMBShare(settings.SMBShare{
		Name: settings.SystemImportShareName, Path: "/mnt/sys_drive/felhom-data/userdata/import",
		System: true,
	}); err != nil {
		t.Fatal(err)
	}
	if err := s.settings.AddSMBShare(settings.SMBShare{
		Name: "csalad", Path: "/mnt/felhom-drives/hdd_1/shares/csalad",
	}); err != nil {
		t.Fatal(err)
	}
	return s
}

// Scenario E, enforcement half: POST the delete endpoint directly. The share must survive.
func TestScenarioE_SystemShareDeleteRefusedServerSide(t *testing.T) {
	s := serverWithImportShare(t)

	rr := httptest.NewRecorder()
	req := httptest.NewRequest("POST", "/sharing/shares/delete",
		strings.NewReader(url.Values{"name": {settings.SystemImportShareName}}.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	s.sharingShareDeleteHandler(rr, req)

	found := false
	for _, sh := range s.settings.GetSMBShares() {
		if strings.EqualFold(sh.Name, settings.SystemImportShareName) {
			found = true
		}
	}
	if !found {
		t.Fatal("the system share was DELETED by a direct POST — the refusal is not server-side")
	}

	// And a non-system share is still deletable, so this is a targeted refusal and not a broken
	// endpoint that happens to refuse everything.
	rr2 := httptest.NewRecorder()
	req2 := httptest.NewRequest("POST", "/sharing/shares/delete",
		strings.NewReader(url.Values{"name": {"csalad"}}.Encode()))
	req2.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	s.sharingShareDeleteHandler(rr2, req2)
	for _, sh := range s.settings.GetSMBShares() {
		if sh.Name == "csalad" {
			t.Error("an ordinary share must still be deletable")
		}
	}
}

// The store layer refuses too, so no future caller can bypass the handler.
func TestScenarioE_StoreLayerRefusesSystemShare(t *testing.T) {
	s := serverWithImportShare(t)
	if err := s.settings.RemoveSMBShare(settings.SystemImportShareName); err == nil {
		t.Error("RemoveSMBShare must refuse a System share")
	}
	if err := s.settings.RemoveSMBShare("csalad"); err != nil {
		t.Errorf("RemoveSMBShare must still delete an ordinary share: %v", err)
	}
}

// Scenario E, reachability half: the template must not render a delete button for a system share —
// and must still render one for an ordinary share.
func TestScenarioE_SharingTemplateOmitsSystemDeleteButton(t *testing.T) {
	data := map[string]interface{}{
		"Page": "sharing", "Title": "Hálózati megosztás",
		"SMBEnabled": true,
		// The SAME type the handler passes — see ShareRow's comment.
		"SMBShares": []ShareRow{
			{Name: settings.SystemImportShareName, Path: "/mnt/sys_drive/felhom-data/userdata/import", System: true, Available: true},
			{Name: "csalad", Path: "/mnt/felhom-drives/hdd_1/shares/csalad", Available: true},
		},
		"CSRFField": "",
	}
	html := renderBackupPage(t, "sharing", data)

	rows := strings.Split(html, "<tr>")
	var sysRow, normalRow string
	for _, row := range rows {
		if strings.Contains(row, settings.SystemImportShareName) {
			sysRow = row
		}
		if strings.Contains(row, "csalad") {
			normalRow = row
		}
	}
	if sysRow == "" || normalRow == "" {
		t.Fatalf("both share rows must render; sys=%v normal=%v", sysRow != "", normalRow != "")
	}
	if strings.Contains(sysRow, "/sharing/shares/delete") {
		t.Error("the system share row must NOT carry a delete form")
	}
	if !strings.Contains(normalRow, "/sharing/shares/delete") {
		t.Error("an ordinary share row must still carry its delete form")
	}
}

// R-75 Scenario F — sharing stays OPT-IN. Deploying a drop-zone app must not put SMB on the LAN.
func TestScenarioF_SharingStaysOptIn(t *testing.T) {
	s := testServer(t)
	if s.settings.GetSMBSettings().Enabled {
		t.Fatal("precondition: sharing must start disabled")
	}
	// The auto-create is wired to the ENABLE handler only; nothing in the deploy path calls it.
	// Assert the state a fresh box is in: no shares at all.
	if got := s.settings.GetSMBShares(); len(got) != 0 {
		t.Errorf("a fresh box must have no shares before sharing is enabled, got %v", got)
	}
	if s.settings.GetSMBSettings().Enabled {
		t.Error("sharing must not have been switched on")
	}
}

// ensureImportShare is idempotent and correctly shaped.
func TestEnsureImportShare_IdempotentAndCorrect(t *testing.T) {
	s := testServer(t)
	// A writable stand-in for /mnt/sys_drive so EnsureImportRoot really creates the dir (the test
	// user is not root, so the real path is not writable).
	s.cfg.Paths.SystemDataPath = t.TempDir()
	s.cfg.Paths.StacksDir = t.TempDir()
	mgr, err := stacks.NewManager(s.cfg, s.logger)
	if err != nil {
		t.Fatal(err)
	}
	s.stackMgr = mgr

	for i := 0; i < 3; i++ {
		if err := s.ensureImportShare(); err != nil {
			t.Fatalf("call %d: %v", i+1, err)
		}
	}
	shares := s.settings.GetSMBShares()
	if len(shares) != 1 {
		t.Fatalf("expected exactly 1 share after 3 calls (idempotent), got %d: %v", len(shares), shares)
	}
	sh := shares[0]
	if sh.Name != settings.SystemImportShareName {
		t.Errorf("share name = %q, want %q", sh.Name, settings.SystemImportShareName)
	}
	if !sh.System {
		t.Error("the import share must be marked System")
	}
	if sh.Offsite {
		t.Error("the drop-zone is class `excluded` — Offsite must be false, or the UI would contradict the backup engines")
	}
	if sh.ReadOnly {
		t.Error("a drop-zone the customer copies INTO must be writable")
	}
	if want := mgr.GetImportRoot(); sh.Path != want {
		t.Errorf("share path = %q, want the canonical import root %q", sh.Path, want)
	}
	// The name must be NetBIOS-safe — it is an SMB share name.
	if err := settings.ValidateSMBShareName(sh.Name); err != nil {
		t.Errorf("share name is not NetBIOS-safe: %v", err)
	}
}

// The brief's re-assertion: SPIKE P4 proved <root>/userdata/import is shareable against a GENERIC
// registered root. This pins the SYSTEM-root shape specifically, because ProtectedHDDPaths has a
// legacy felhom-data double-nest branch that only fires there.
//
// It documents the actual live shape, which is why ensureImportShare does not route through the
// picker guard: the system drive is NOT a registered storage path on either demo box (verified
// 2026-07-26), so sharingResolvePath — whose job is to validate CUSTOMER-supplied paths — refuses it.
// A controller-generated constant is a different trust class.
func TestImportRoot_NotReachableViaTheCustomerPicker(t *testing.T) {
	s := testServer(t)
	root := t.TempDir() // stands in for the system drive; deliberately NOT registered
	importRoot := filepath.Join(root, "felhom-data", "userdata", "import")
	if err := os.MkdirAll(importRoot, 0o755); err != nil {
		t.Fatal(err)
	}
	// A registered data drive exists, so this is not "the registry is empty" trivially refusing.
	dataDrive := t.TempDir()
	if err := os.MkdirAll(filepath.Join(dataDrive, "userdata", "import"), 0o755); err != nil {
		t.Fatal(err)
	}
	if err := s.settings.AddStoragePath(settings.StoragePath{Path: dataDrive, Label: "hdd_1", IsDefault: true}); err != nil {
		t.Fatal(err)
	}

	if _, err := s.sharingResolvePath(importRoot); err == nil {
		t.Error("the customer picker must NOT accept the unregistered system-drive import root")
	}
	// Control: the data drive's own userdata subtree IS pickable, so the guard is not refusing all.
	if _, err := s.sharingResolvePath(filepath.Join(dataDrive, "userdata", "import")); err != nil {
		t.Errorf("a registered drive's userdata/import must stay shareable: %v", err)
	}
}
