package app

import (
	"io"
	"log/slog"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"git.warky.dev/wdevs/amcs/internal/auth"
	"git.warky.dev/wdevs/amcs/internal/config"

	"github.com/bitechdev/ResolveSpec/pkg/resolvespec"
)

func TestResolveSpecAuthRequiresValidCredentials(t *testing.T) {
	keyring, err := auth.NewKeyring([]config.APIKey{{ID: "operator", Value: "secret"}})
	if err != nil {
		t.Fatalf("NewKeyring() error = %v", err)
	}

	logger := slog.New(slog.NewTextHandler(io.Discard, nil))
	protected := auth.Middleware(config.AuthConfig{}, keyring, nil, nil, nil, logger)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/api/rs/public/projects" {
			t.Fatalf("path = %q, want /api/rs/public/projects", r.URL.Path)
		}
		w.WriteHeader(http.StatusNoContent)
	}))

	t.Run("missing credentials are rejected", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodPost, "/api/rs/public/projects", strings.NewReader(`{"operation":"read"}`))
		rec := httptest.NewRecorder()

		protected.ServeHTTP(rec, req)

		if rec.Code != http.StatusUnauthorized {
			t.Fatalf("status = %d, want %d", rec.Code, http.StatusUnauthorized)
		}
	})

	t.Run("valid API key is accepted", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodPost, "/api/rs/public/projects", strings.NewReader(`{"operation":"read"}`))
		req.Header.Set("x-brain-key", "secret")
		rec := httptest.NewRecorder()

		protected.ServeHTTP(rec, req)

		if rec.Code != http.StatusNoContent {
			t.Fatalf("status = %d, want %d", rec.Code, http.StatusNoContent)
		}
	})
}

func TestResolveSpecGuardAllowsSupportedMutations(t *testing.T) {
	rs := resolvespec.NewHandler(nil, nil)
	registerResolveSpecGuards(rs)

	cases := []struct {
		name      string
		entity    string
		operation string
	}{
		{name: "learnings read", entity: "learnings", operation: "read"},
		{name: "projects create", entity: "projects", operation: "create"},
		{name: "projects update", entity: "projects", operation: "update"},
		{name: "projects delete", entity: "projects", operation: "delete"},
		{name: "plans create", entity: "plans", operation: "create"},
		{name: "plans update", entity: "plans", operation: "update"},
		{name: "plans delete", entity: "plans", operation: "delete"},
		{name: "learnings create", entity: "learnings", operation: "create"},
		{name: "learnings update", entity: "learnings", operation: "update"},
		{name: "learnings delete", entity: "learnings", operation: "delete"},
		{name: "thoughts create", entity: "thoughts", operation: "create"},
		{name: "thoughts update", entity: "thoughts", operation: "update"},
		{name: "thoughts delete", entity: "thoughts", operation: "delete"},
		{name: "agent_skills create", entity: "agent_skills", operation: "create"},
		{name: "agent_skills update", entity: "agent_skills", operation: "update"},
		{name: "agent_skills delete", entity: "agent_skills", operation: "delete"},
		{name: "agent_guardrails create", entity: "agent_guardrails", operation: "create"},
		{name: "agent_guardrails update", entity: "agent_guardrails", operation: "update"},
		{name: "agent_guardrails delete", entity: "agent_guardrails", operation: "delete"},
		{name: "stored_files update", entity: "stored_files", operation: "update"},
		{name: "stored_files delete", entity: "stored_files", operation: "delete"},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			hookCtx := &resolvespec.HookContext{
				Schema:    "public",
				Entity:    tc.entity,
				Operation: tc.operation,
			}

			err := rs.Hooks().Execute(resolvespec.BeforeHandle, hookCtx)
			if err != nil {
				t.Fatalf("Execute() error = %v, want nil", err)
			}
			if hookCtx.Abort {
				t.Fatalf("Abort = true, want false (code=%d message=%q)", hookCtx.AbortCode, hookCtx.AbortMessage)
			}
		})
	}
}

func TestResolveSpecGuardBlocksUnsupportedMutations(t *testing.T) {
	rs := resolvespec.NewHandler(nil, nil)
	registerResolveSpecGuards(rs)

	cases := []struct {
		name          string
		entity        string
		operation     string
		wantCode      int
		wantMessageIn string
	}{
		{
			name:          "mutations blocked for non-allowlisted operation",
			entity:        "stored_files",
			operation:     "create",
			wantCode:      http.StatusForbidden,
			wantMessageIn: `operation "create" is not allowed for public.stored_files`,
		},
		{
			name:          "mutations blocked for non-allowlisted entity",
			entity:        "maintenance_logs",
			operation:     "delete",
			wantCode:      http.StatusForbidden,
			wantMessageIn: `operation "delete" is not allowed for public.maintenance_logs`,
		},
		{
			name:          "unknown operation is rejected",
			entity:        "projects",
			operation:     "scan",
			wantCode:      http.StatusBadRequest,
			wantMessageIn: `unsupported operation "scan"`,
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			hookCtx := &resolvespec.HookContext{
				Schema:    "public",
				Entity:    tc.entity,
				Operation: tc.operation,
			}

			err := rs.Hooks().Execute(resolvespec.BeforeHandle, hookCtx)
			if err == nil {
				t.Fatal("Execute() error = nil, want non-nil")
			}
			if !hookCtx.Abort {
				t.Fatal("Abort = false, want true")
			}
			if hookCtx.AbortCode != tc.wantCode {
				t.Fatalf("AbortCode = %d, want %d", hookCtx.AbortCode, tc.wantCode)
			}
			if !strings.Contains(hookCtx.AbortMessage, tc.wantMessageIn) {
				t.Fatalf("AbortMessage = %q, want substring %q", hookCtx.AbortMessage, tc.wantMessageIn)
			}
		})
	}
}

func TestResolveSpecModelsIncludeLearnings(t *testing.T) {
	models := resolveSpecModels()
	for _, model := range models {
		if model.schema == "public" && model.entity == "learnings" {
			return
		}
	}
	t.Fatal("resolveSpecModels() missing public.learnings")
}
