package handler

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"path/filepath"
	"testing"

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

func TestIsReviewingPath(t *testing.T) {
	cases := []struct {
		path         string
		wantOK       bool
		wantProj     string
		wantTracking string
		wantSide     string
	}{
		{"/Project/reviewing/", true, "Project", "", ""},
		{"/Project/reviewing/123-EM-SUB-0001/", true, "Project", "123-EM-SUB-0001", ""},
		// Case-insensitive on the literal "reviewing" segment.
		{"/Project/Reviewing/", true, "Project", "", ""},
		{"/Project/REVIEWING/x/", true, "Project", "x", ""},
		// No trailing slash: still classified (caller decides redirect).
		{"/Project/reviewing", true, "Project", "", ""},
		{"/Project/reviewing/123/", true, "Project", "123", ""},
		// Depth 2+: side present.
		{"/Project/reviewing/123/received/", true, "Project", "123", "received"},
		{"/Project/reviewing/123/staged/", true, "Project", "123", "staged"},
		{"/Project/reviewing/123/received/sub/", true, "Project", "123", "received/sub"},
		// Unknown side at depth 2 is rejected.
		{"/Project/reviewing/123/issued/", false, "", "", ""},
		// Non-canonical / wrong shape.
		{"/Project/", false, "", "", ""},
		{"/", false, "", "", ""},
		{"/Project/working/", false, "", "", ""},
	}
	for _, tc := range cases {
		gotProj, gotTracking, gotSide, gotOK := IsReviewingPath(tc.path)
		if gotOK != tc.wantOK || gotProj != tc.wantProj || gotTracking != tc.wantTracking || gotSide != tc.wantSide {
			t.Errorf("IsReviewingPath(%q) = (%q,%q,%q,%v), want (%q,%q,%q,%v)",
				tc.path, gotProj, gotTracking, gotSide, gotOK, tc.wantProj, tc.wantTracking, tc.wantSide, tc.wantOK)
		}
	}
}

// Test setup: build a synthetic project tree with two parties, one
// pending submittal each. Verify the aggregator returns:
//   - depth 0: 2 virtual <tracking>/ entries, sorted, both with
//     URLs under /<project>/reviewing/
//   - depth 1: received/ + staged/ entries with canonical URLs
func TestServeReviewing(t *testing.T) {
	root := t.TempDir()
	mustWrite(t, filepath.Join(root, ".zddc"),
		"acl:\n  permissions:\n    \"*\": rwcda\n")

	// Two parties under archive/, each with a pending submittal.
	// Acme: submitted but no response staged or issued yet.
	// Beta: submitted, response staged but not yet issued.
	pAcmeReceived := filepath.Join(root, "Project", "archive", "Acme", "received",
		"2025-10-31_001-AB-SUB-0001 (IFR) - Pending acme review")
	pBetaReceived := filepath.Join(root, "Project", "archive", "Beta", "received",
		"2025-11-01_002-AB-SUB-0007 (IFR) - Pending beta review")
	pBetaStaged := filepath.Join(root, "Project", "staging",
		"2025-11-15_002-AB-SUB-0007 (RSC) - Beta response draft")
	for _, p := range []string{pAcmeReceived, pBetaReceived, pBetaStaged} {
		mustMkdir(t, p)
	}
	// And a third party (Gamma) where the submittal has BEEN issued —
	// should NOT appear in the pending list.
	pGammaReceived := filepath.Join(root, "Project", "archive", "Gamma", "received",
		"2025-09-01_003-CD-SUB-0099 (IFR) - Already responded")
	pGammaIssued := filepath.Join(root, "Project", "archive", "Gamma", "issued",
		"2025-09-15_003-CD-SUB-0099 (RSC) - The response we sent")
	mustMkdir(t, pGammaReceived)
	mustMkdir(t, pGammaIssued)

	zddc.InvalidateCache(root)

	cfg := config.Config{
		Root:        root,
		EmailHeader: "X-Auth-Request-Email",
	}

	t.Run("depth-0 lists pending submittals only", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/Project/reviewing/", nil)
		req.Header.Set("Accept", "application/json")
		req = req.WithContext(WithEmail(req.Context(), "alice@example.com"))
		rec := httptest.NewRecorder()
		ServeReviewing(cfg, rec, req, "Project", "", "")

		if rec.Code != http.StatusOK {
			t.Fatalf("status=%d, body=%s", rec.Code, rec.Body.String())
		}
		var got []listing.FileInfo
		if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil {
			t.Fatalf("decode: %v; body=%s", err, rec.Body.String())
		}
		if len(got) != 2 {
			t.Fatalf("got %d entries, want 2 (Acme + Beta pending; Gamma issued); body=%s",
				len(got), rec.Body.String())
		}
		// Sorted by tracking number → 001-* before 002-*.
		if got[0].Name != "001-AB-SUB-0001/" {
			t.Errorf("entries[0].Name=%q, want %q", got[0].Name, "001-AB-SUB-0001/")
		}
		if got[1].Name != "002-AB-SUB-0007/" {
			t.Errorf("entries[1].Name=%q, want %q", got[1].Name, "002-AB-SUB-0007/")
		}
		for i, e := range got {
			if !e.IsDir || !e.Virtual {
				t.Errorf("entries[%d] IsDir=%v Virtual=%v, want both true", i, e.IsDir, e.Virtual)
			}
			// Per-submittal URL stays under reviewing/ (the user can
			// drill into the per-submittal received/+staged/ view).
			if e.URL != "/Project/reviewing/"+got[i].Name[:len(got[i].Name)-1]+"/" {
				t.Errorf("entries[%d].URL=%q, want under /Project/reviewing/", i, e.URL)
			}
		}
	})

	t.Run("depth-1 with staged draft → received/ + staged/", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/Project/reviewing/002-AB-SUB-0007/", nil)
		req.Header.Set("Accept", "application/json")
		req = req.WithContext(WithEmail(req.Context(), "alice@example.com"))
		rec := httptest.NewRecorder()
		ServeReviewing(cfg, rec, req, "Project", "002-AB-SUB-0007", "")

		if rec.Code != http.StatusOK {
			t.Fatalf("status=%d, body=%s", rec.Code, rec.Body.String())
		}
		var got []listing.FileInfo
		if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil {
			t.Fatalf("decode: %v", err)
		}
		if len(got) != 2 {
			t.Fatalf("got %d entries, want 2 (received/ + staged/); body=%s",
				len(got), rec.Body.String())
		}
		if got[0].Name != "received/" {
			t.Errorf("entries[0].Name=%q, want %q", got[0].Name, "received/")
		}
		// Virtual URL — stays under reviewing/ so depth-2 navigation
		// returns to the aggregator (which lists the real folder's
		// contents with canonical file URLs).
		if want := "/Project/reviewing/002-AB-SUB-0007/received/"; got[0].URL != want {
			t.Errorf("received URL=%q, want %q", got[0].URL, want)
		}
		if got[1].Name != "staged/" {
			t.Errorf("entries[1].Name=%q, want %q", got[1].Name, "staged/")
		}
		if want := "/Project/reviewing/002-AB-SUB-0007/staged/"; got[1].URL != want {
			t.Errorf("staged URL=%q, want %q", got[1].URL, want)
		}
	})

	t.Run("depth-1 with no staged draft → received/ only", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/Project/reviewing/001-AB-SUB-0001/", nil)
		req.Header.Set("Accept", "application/json")
		req = req.WithContext(WithEmail(req.Context(), "alice@example.com"))
		rec := httptest.NewRecorder()
		ServeReviewing(cfg, rec, req, "Project", "001-AB-SUB-0001", "")

		if rec.Code != http.StatusOK {
			t.Fatalf("status=%d, body=%s", rec.Code, rec.Body.String())
		}
		var got []listing.FileInfo
		if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil {
			t.Fatalf("decode: %v", err)
		}
		if len(got) != 1 || got[0].Name != "received/" {
			t.Fatalf("got %+v, want [received/] only (no draft)", got)
		}
	})

	t.Run("depth-1 unknown tracking → 404", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/Project/reviewing/999-ZZ-SUB-9999/", nil)
		req.Header.Set("Accept", "application/json")
		req = req.WithContext(WithEmail(req.Context(), "alice@example.com"))
		rec := httptest.NewRecorder()
		ServeReviewing(cfg, rec, req, "Project", "999-ZZ-SUB-9999", "")
		if rec.Code != http.StatusNotFound {
			t.Errorf("status=%d, want 404", rec.Code)
		}
	})

	t.Run("missing archive/ entirely → empty depth-0 listing", func(t *testing.T) {
		// Fresh project state: no archive/ subtree at all.
		bareRoot := t.TempDir()
		mustWrite(t, filepath.Join(bareRoot, ".zddc"),
			"acl:\n  permissions:\n    \"*\": rwcda\n")
		mustMkdir(t, filepath.Join(bareRoot, "Fresh"))
		zddc.InvalidateCache(bareRoot)
		bareCfg := config.Config{Root: bareRoot, EmailHeader: "X-Auth-Request-Email"}

		req := httptest.NewRequest(http.MethodGet, "/Fresh/reviewing/", nil)
		req.Header.Set("Accept", "application/json")
		req = req.WithContext(WithEmail(req.Context(), "alice@example.com"))
		rec := httptest.NewRecorder()
		ServeReviewing(bareCfg, rec, req, "Fresh", "", "")
		if rec.Code != http.StatusOK {
			t.Fatalf("status=%d, body=%s", rec.Code, rec.Body.String())
		}
		body := rec.Body.String()
		// Empty array, not "null".
		if body == "null" || body == "null\n" {
			t.Errorf("body=%q, want []; nil-slice encoded as null", body)
		}
	})
}

// startsWith — local helper. mustMkdir / mustWrite live in
// formhandler_test.go and are reused here.
func startsWith(s, prefix string) bool {
	return len(s) >= len(prefix) && s[:len(prefix)] == prefix
}
