package handler

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

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

// projectCreateFixture lays out a deployment whose root .zddc grants
// `c` (create) to `*@example.com` so non-admin members can spin up
// projects, with `admin@example.com` as the super-admin escape hatch.
func projectCreateFixture(t *testing.T) (config.Config, string) {
	t.Helper()
	root := t.TempDir()
	mustWriteHelper(t, filepath.Join(root, ".zddc"),
		"admins:\n  - admin@example.com\n"+
			"acl:\n  permissions:\n    \"*@example.com\": c\n")
	zddc.InvalidateCache(root)
	return config.Config{Root: root, EmailHeader: "X-Auth-Request-Email", MaxWriteBytes: 64 * 1024}, root
}

func doProjectCreate(cfg config.Config, email string, elevated bool, body []byte) *httptest.ResponseRecorder {
	req := httptest.NewRequest(http.MethodPost, "/.profile/projects", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	ctx := context.WithValue(req.Context(), EmailKey, email)
	ctx = context.WithValue(ctx, ElevatedKey, elevated)
	req = req.WithContext(ctx)
	rec := httptest.NewRecorder()
	serveProfileProjectsCreate(cfg, rec, req)
	return rec
}

// Non-admin user holds `c` at root via cascade — succeeds, becomes
// subtree admin of the new project.
func TestProjectCreate_CascadeGrantedUserBecomesSubtreeAdmin(t *testing.T) {
	cfg, root := projectCreateFixture(t)
	body, _ := json.Marshal(map[string]any{"parent": "/", "name": "Project-1", "title": "My project"})
	rec := doProjectCreate(cfg, "alice@example.com", false, body)
	if rec.Code != http.StatusCreated {
		t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
	}
	zddc.InvalidateCache(root)
	// Verify the new project .zddc names alice as admin.
	zf, err := zddc.ParseFile(filepath.Join(root, "Project-1", ".zddc"))
	if err != nil {
		t.Fatalf("read new .zddc: %v", err)
	}
	if len(zf.Admins) != 1 || zf.Admins[0] != "alice@example.com" {
		t.Errorf("Admins=%v, want [alice@example.com]", zf.Admins)
	}
	if zf.Title != "My project" {
		t.Errorf("Title=%q, want %q", zf.Title, "My project")
	}
}

// User with no grant at root — 404 (existence-hidden).
func TestProjectCreate_UnauthenticatedUserDenied(t *testing.T) {
	cfg, _ := projectCreateFixture(t)
	body, _ := json.Marshal(map[string]any{"parent": "/", "name": "ShouldFail"})
	rec := doProjectCreate(cfg, "stranger@other.org", false, body)
	if rec.Code != http.StatusNotFound {
		t.Errorf("status=%d, want 404", rec.Code)
	}
}

// Super-admin elevated — succeeds via decider's IsActiveAdmin bypass,
// not via explicit ACL grant. Admins default to creator email.
func TestProjectCreate_ElevatedSuperAdminWithDefaultAdmins(t *testing.T) {
	cfg, root := projectCreateFixture(t)
	body, _ := json.Marshal(map[string]any{"parent": "/", "name": "AdminProj"})
	rec := doProjectCreate(cfg, "admin@example.com", true, body)
	if rec.Code != http.StatusCreated {
		t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
	}
	zddc.InvalidateCache(root)
	zf, _ := zddc.ParseFile(filepath.Join(root, "AdminProj", ".zddc"))
	if len(zf.Admins) != 1 || zf.Admins[0] != "admin@example.com" {
		t.Errorf("default Admins=%v, want [admin@example.com]", zf.Admins)
	}
}

// Caller passes explicit admins list — used instead of auto-defaulting.
func TestProjectCreate_ExplicitAdminsListWins(t *testing.T) {
	cfg, root := projectCreateFixture(t)
	body, _ := json.Marshal(map[string]any{
		"parent": "/",
		"name":   "TeamProj",
		"admins": []string{"alice@example.com", "bob@example.com"},
	})
	rec := doProjectCreate(cfg, "alice@example.com", false, body)
	if rec.Code != http.StatusCreated {
		t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
	}
	zddc.InvalidateCache(root)
	zf, _ := zddc.ParseFile(filepath.Join(root, "TeamProj", ".zddc"))
	if len(zf.Admins) != 2 {
		t.Errorf("Admins=%v, want 2 entries", zf.Admins)
	}
}

// Re-creating an existing project name fails 409 — duplicate detection
// runs before the admin seed write.
func TestProjectCreate_DuplicateNameRejected(t *testing.T) {
	cfg, root := projectCreateFixture(t)
	if err := os.MkdirAll(filepath.Join(root, "Existing"), 0o755); err != nil {
		t.Fatal(err)
	}
	body, _ := json.Marshal(map[string]any{"parent": "/", "name": "Existing"})
	rec := doProjectCreate(cfg, "alice@example.com", false, body)
	if rec.Code != http.StatusConflict {
		t.Errorf("status=%d, want 409", rec.Code)
	}
}
