package handler

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

	"codeberg.org/VARASYS/ZDDC/zddc/internal/config"
	"codeberg.org/VARASYS/ZDDC/zddc/internal/zddc"
)

// ssrTestSetup builds a fresh project root with permissive top-level
// ACL that lets *@example.com create + write anywhere under archive/.
// Returns (cfg, do) where do dispatches a request through the same
// recognize→serve path the production catch-all uses.
func ssrTestSetup(t *testing.T) (config.Config, func(method, target, email, body string, headers map[string]string) *httptest.ResponseRecorder) {
	t.Helper()
	root := t.TempDir()
	// Project root: grant the test cohort rwc at the project level so
	// they can create archive/<party>/ folders.
	if err := os.WriteFile(filepath.Join(root, ".zddc"),
		[]byte("acl:\n  permissions:\n    \"*@example.com\": rwcd\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	zddc.InvalidateCache(root)
	cfg := config.Config{Root: root, EmailHeader: "X-Auth-Request-Email"}

	do := func(method, target, email, body string, headers map[string]string) *httptest.ResponseRecorder {
		var req *http.Request
		if body != "" {
			req = httptest.NewRequest(method, target, bytes.NewReader([]byte(body)))
			req.Header.Set("Content-Type", "application/json")
		} else {
			req = httptest.NewRequest(method, target, nil)
		}
		for k, v := range headers {
			req.Header.Set(k, v)
		}
		ctx := context.WithValue(req.Context(), EmailKey, email)
		ctx = context.WithValue(ctx, ElevatedKey, true)
		req = req.WithContext(ctx)
		rec := httptest.NewRecorder()

		// SSR create flows through RecognizeFormRequest → ServeForm →
		// create-via-ssr case. Rename flows through ServeFileAPI's POST
		// dispatch (ssr-rename op).
		if method == http.MethodPost && strings.Contains(target, "/ssr/") &&
			strings.HasSuffix(target, ".yaml") {
			ServeFileAPI(cfg, rec, req)
			return rec
		}
		formReq := RecognizeFormRequest(cfg.Root, method, target)
		if formReq != nil {
			ServeForm(cfg, formReq, rec, req)
			return rec
		}
		rec.WriteHeader(http.StatusNotFound)
		return rec
	}
	return cfg, do
}

func TestSSRCreate_HappyPath(t *testing.T) {
	cfg, do := ssrTestSetup(t)

	body := `{"name":"0330C1","vendorType":"subcontractor","contractNo":"PO-001","scopeSummary":"Concrete works"}`
	rec := do(http.MethodPost, "/Project/ssr/form.html", "casey@example.com", body, nil)
	if rec.Code != http.StatusCreated {
		t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
	}
	if loc := rec.Result().Header.Get("Location"); loc != "/Project/ssr/0330C1.yaml" {
		t.Errorf("Location=%q want /Project/ssr/0330C1.yaml", loc)
	}
	// archive/0330C1/ exists.
	partyDir := filepath.Join(cfg.Root, "Project", "archive", "0330C1")
	if info, err := os.Stat(partyDir); err != nil || !info.IsDir() {
		t.Fatalf("party folder not created: err=%v", err)
	}
	// .zddc auto-own grant.
	zf, err := os.ReadFile(filepath.Join(partyDir, ".zddc"))
	if err != nil {
		t.Fatalf("read auto-own .zddc: %v", err)
	}
	if !strings.Contains(string(zf), "casey@example.com") {
		t.Errorf("auto-own .zddc missing creator email; got %s", string(zf))
	}
	// ssr.yaml exists and contains the submitted fields but NOT `name`.
	yamlBytes, err := os.ReadFile(filepath.Join(partyDir, "ssr.yaml"))
	if err != nil {
		t.Fatalf("read ssr.yaml: %v", err)
	}
	yaml := string(yamlBytes)
	if !strings.Contains(yaml, "contractNo: PO-001") {
		t.Errorf("ssr.yaml missing contractNo; got %s", yaml)
	}
	if strings.Contains(yaml, "name: 0330C1") {
		t.Errorf("ssr.yaml should not carry path-derived `name` field; got %s", yaml)
	}
}

func TestSSRCreate_AnonymousRejected(t *testing.T) {
	_, do := ssrTestSetup(t)
	body := `{"name":"0330C1","vendorType":"subcontractor","contractNo":"PO-001","scopeSummary":"x"}`
	rec := do(http.MethodPost, "/Project/ssr/form.html", "", body, nil)
	if rec.Code != http.StatusUnauthorized {
		t.Errorf("status=%d want 401; body=%s", rec.Code, rec.Body.String())
	}
}

func TestSSRCreate_InvalidName(t *testing.T) {
	_, do := ssrTestSetup(t)
	cases := []string{
		`{"name":".hidden","vendorType":"subcontractor","contractNo":"x","scopeSummary":"x"}`,
		`{"name":"with space","vendorType":"subcontractor","contractNo":"x","scopeSummary":"x"}`,
	}
	for _, body := range cases {
		rec := do(http.MethodPost, "/Project/ssr/form.html", "casey@example.com", body, nil)
		if rec.Code != http.StatusUnprocessableEntity && rec.Code != http.StatusBadRequest {
			t.Errorf("body=%s status=%d want 422 or 400", body, rec.Code)
		}
	}
}

func TestSSRCreate_DuplicateName(t *testing.T) {
	cfg, do := ssrTestSetup(t)
	body := `{"name":"0330C1","vendorType":"subcontractor","contractNo":"PO-001","scopeSummary":"x"}`
	rec := do(http.MethodPost, "/Project/ssr/form.html", "casey@example.com", body, nil)
	if rec.Code != http.StatusCreated {
		t.Fatalf("first create failed: status=%d body=%s", rec.Code, rec.Body.String())
	}
	zddc.InvalidateCache(filepath.Join(cfg.Root, "Project", "archive", "0330C1"))
	rec = do(http.MethodPost, "/Project/ssr/form.html", "casey@example.com", body, nil)
	if rec.Code != http.StatusConflict {
		t.Errorf("duplicate create: status=%d want 409", rec.Code)
	}
}

func TestSSRRename_HappyPath(t *testing.T) {
	cfg, do := ssrTestSetup(t)
	body := `{"name":"0330C1","vendorType":"subcontractor","contractNo":"PO-001","scopeSummary":"x"}`
	if rec := do(http.MethodPost, "/Project/ssr/form.html", "casey@example.com", body, nil); rec.Code != http.StatusCreated {
		t.Fatalf("setup create failed: %d %s", rec.Code, rec.Body.String())
	}
	// Drop an MDL row inside the party folder; it should survive the rename.
	mdlDir := filepath.Join(cfg.Root, "Project", "archive", "0330C1", "mdl")
	if err := os.MkdirAll(mdlDir, 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(mdlDir, "D-001.yaml"), []byte("id: D-001\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	zddc.InvalidateCache(filepath.Join(cfg.Root, "Project", "archive", "0330C1"))

	rec := do(http.MethodPost, "/Project/ssr/0330C1.yaml", "casey@example.com", "",
		map[string]string{
			"X-ZDDC-Op":          opSSRRename,
			"X-ZDDC-Destination": "/Project/ssr/0330C2.yaml",
		})
	if rec.Code != http.StatusOK {
		t.Fatalf("rename failed: %d body=%s", rec.Code, rec.Body.String())
	}
	if _, err := os.Stat(filepath.Join(cfg.Root, "Project", "archive", "0330C1")); !os.IsNotExist(err) {
		t.Error("source party folder still exists after rename")
	}
	if _, err := os.Stat(filepath.Join(cfg.Root, "Project", "archive", "0330C2")); err != nil {
		t.Errorf("destination party folder not created: %v", err)
	}
	// MDL row followed the directory rename.
	if _, err := os.Stat(filepath.Join(cfg.Root, "Project", "archive", "0330C2", "mdl", "D-001.yaml")); err != nil {
		t.Errorf("MDL row did not survive rename: %v", err)
	}
}

func TestSSRRename_CrossProjectRejected(t *testing.T) {
	cfg, do := ssrTestSetup(t)
	body := `{"name":"0330C1","vendorType":"subcontractor","contractNo":"PO-001","scopeSummary":"x"}`
	if rec := do(http.MethodPost, "/Project/ssr/form.html", "casey@example.com", body, nil); rec.Code != http.StatusCreated {
		t.Fatalf("setup create failed: %d", rec.Code)
	}
	zddc.InvalidateCache(filepath.Join(cfg.Root, "Project"))
	rec := do(http.MethodPost, "/Project/ssr/0330C1.yaml", "casey@example.com", "",
		map[string]string{
			"X-ZDDC-Op":          opSSRRename,
			"X-ZDDC-Destination": "/OtherProject/ssr/0330C1.yaml",
		})
	if rec.Code != http.StatusBadRequest {
		t.Errorf("cross-project rename: status=%d want 400", rec.Code)
	}
}

func TestSSRRename_DestinationExists(t *testing.T) {
	cfg, do := ssrTestSetup(t)
	bodyA := `{"name":"0330C1","vendorType":"subcontractor","contractNo":"PO-001","scopeSummary":"x"}`
	bodyB := `{"name":"0330C2","vendorType":"subcontractor","contractNo":"PO-002","scopeSummary":"y"}`
	for _, b := range []string{bodyA, bodyB} {
		if rec := do(http.MethodPost, "/Project/ssr/form.html", "casey@example.com", b, nil); rec.Code != http.StatusCreated {
			t.Fatalf("setup create failed: %d %s", rec.Code, rec.Body.String())
		}
	}
	zddc.InvalidateCache(filepath.Join(cfg.Root, "Project"))
	rec := do(http.MethodPost, "/Project/ssr/0330C1.yaml", "casey@example.com", "",
		map[string]string{
			"X-ZDDC-Op":          opSSRRename,
			"X-ZDDC-Destination": "/Project/ssr/0330C2.yaml",
		})
	if rec.Code != http.StatusConflict {
		t.Errorf("rename to existing: status=%d want 409", rec.Code)
	}
}
