package handlers

import (
	"encoding/json"
	"net/http"
	"testing"

	"github.com/gin-gonic/gin"

	"chat-switchboard/config"
	authpkg "chat-switchboard/auth"
	"chat-switchboard/database"
	"chat-switchboard/middleware"
	"chat-switchboard/store"
	postgres "chat-switchboard/store/postgres"
	sqlite "chat-switchboard/store/sqlite"
)

// TestWorkflowCRUD exercises the full workflow lifecycle:
// create → add stages → publish → start instance → advance → complete.
func TestWorkflowCRUD(t *testing.T) {
	h := setupWorkflowHarness(t)

	// ── Create workflow ─────────────────────

	resp := h.request("POST", "/api/v1/workflows", h.adminToken, map[string]interface{}{
		"name":        "Customer Intake",
		"slug":        "customer-intake",
		"description": "Collect customer info and route to support",
		"entry_mode":  "public_link",
	})
	if resp.Code != http.StatusCreated {
		t.Fatalf("Create workflow: got %d, body: %s", resp.Code, resp.Body.String())
	}

	var wf struct {
		ID      string `json:"id"`
		Slug    string `json:"slug"`
		Version int    `json:"version"`
	}
	json.Unmarshal(resp.Body.Bytes(), &wf)
	if wf.ID == "" {
		t.Fatal("workflow ID is empty")
	}
	if wf.Slug != "customer-intake" {
		t.Errorf("slug: got %q, want %q", wf.Slug, "customer-intake")
	}
	if wf.Version != 1 {
		t.Errorf("version: got %d, want 1", wf.Version)
	}

	// ── Get workflow ────────────────────────

	resp = h.request("GET", "/api/v1/workflows/"+wf.ID, h.adminToken, nil)
	if resp.Code != http.StatusOK {
		t.Fatalf("Get workflow: got %d, body: %s", resp.Code, resp.Body.String())
	}

	// ── List workflows ──────────────────────

	resp = h.request("GET", "/api/v1/workflows", h.adminToken, nil)
	if resp.Code != http.StatusOK {
		t.Fatalf("List workflows: got %d", resp.Code)
	}
	var listResp struct {
		Data []struct{ ID string } `json:"data"`
	}
	json.Unmarshal(resp.Body.Bytes(), &listResp)
	if len(listResp.Data) == 0 {
		t.Error("List workflows returned empty")
	}

	// ── Add stages ──────────────────────────

	resp = h.request("POST", "/api/v1/workflows/"+wf.ID+"/stages", h.adminToken, map[string]interface{}{
		"name":         "Collect Info",
		"history_mode": "full",
		"ordinal":      0,
	})
	if resp.Code != http.StatusCreated {
		t.Fatalf("Create stage 0: got %d, body: %s", resp.Code, resp.Body.String())
	}
	var stage0 struct{ ID string `json:"id"` }
	json.Unmarshal(resp.Body.Bytes(), &stage0)

	resp = h.request("POST", "/api/v1/workflows/"+wf.ID+"/stages", h.adminToken, map[string]interface{}{
		"name":         "Review",
		"history_mode": "fresh",
		"ordinal":      1,
	})
	if resp.Code != http.StatusCreated {
		t.Fatalf("Create stage 1: got %d, body: %s", resp.Code, resp.Body.String())
	}
	var stage1 struct{ ID string `json:"id"` }
	json.Unmarshal(resp.Body.Bytes(), &stage1)

	// ── List stages ─────────────────────────

	resp = h.request("GET", "/api/v1/workflows/"+wf.ID+"/stages", h.adminToken, nil)
	if resp.Code != http.StatusOK {
		t.Fatalf("List stages: got %d", resp.Code)
	}
	var stagesResp struct {
		Data []struct {
			ID      string `json:"id"`
			Ordinal int    `json:"ordinal"`
		} `json:"data"`
	}
	json.Unmarshal(resp.Body.Bytes(), &stagesResp)
	if len(stagesResp.Data) != 2 {
		t.Fatalf("List stages: got %d, want 2", len(stagesResp.Data))
	}

	// ── Reorder stages ──────────────────────

	resp = h.request("PATCH", "/api/v1/workflows/"+wf.ID+"/stages/reorder", h.adminToken, map[string]interface{}{
		"ordered_ids": []string{stage1.ID, stage0.ID},
	})
	if resp.Code != http.StatusOK {
		t.Fatalf("Reorder stages: got %d, body: %s", resp.Code, resp.Body.String())
	}

	// ── Update workflow ─────────────────────

	resp = h.request("PATCH", "/api/v1/workflows/"+wf.ID, h.adminToken, map[string]interface{}{
		"is_active": true,
	})
	if resp.Code != http.StatusOK {
		t.Fatalf("Update workflow: got %d, body: %s", resp.Code, resp.Body.String())
	}

	// Re-read to get incremented version
	resp = h.request("GET", "/api/v1/workflows/"+wf.ID, h.adminToken, nil)
	json.Unmarshal(resp.Body.Bytes(), &wf)
	if wf.Version != 2 {
		t.Errorf("version after update: got %d, want 2", wf.Version)
	}

	// ── Publish ─────────────────────────────

	resp = h.request("POST", "/api/v1/workflows/"+wf.ID+"/publish", h.adminToken, nil)
	if resp.Code != http.StatusCreated {
		t.Fatalf("Publish: got %d, body: %s", resp.Code, resp.Body.String())
	}
	var ver struct {
		VersionNumber int             `json:"version_number"`
		Snapshot      json.RawMessage `json:"snapshot"`
	}
	json.Unmarshal(resp.Body.Bytes(), &ver)
	if ver.VersionNumber != 2 {
		t.Errorf("published version: got %d, want 2", ver.VersionNumber)
	}
	if len(ver.Snapshot) == 0 {
		t.Error("snapshot is empty")
	}

	// ── Duplicate publish should conflict ───

	resp = h.request("POST", "/api/v1/workflows/"+wf.ID+"/publish", h.adminToken, nil)
	if resp.Code != http.StatusConflict {
		t.Errorf("Duplicate publish: got %d, want 409", resp.Code)
	}

	// ── Get version ─────────────────────────

	resp = h.request("GET", "/api/v1/workflows/"+wf.ID+"/versions/2", h.adminToken, nil)
	if resp.Code != http.StatusOK {
		t.Fatalf("Get version: got %d", resp.Code)
	}

	// ── Duplicate slug should conflict ──────

	resp = h.request("POST", "/api/v1/workflows", h.adminToken, map[string]interface{}{
		"name": "Another Intake",
		"slug": "customer-intake",
	})
	if resp.Code != http.StatusConflict {
		t.Errorf("Duplicate slug: got %d, want 409", resp.Code)
	}

	// ── Auto-slug from name ─────────────────

	resp = h.request("POST", "/api/v1/workflows", h.adminToken, map[string]interface{}{
		"name": "Bug Report Triage",
	})
	if resp.Code != http.StatusCreated {
		t.Fatalf("Auto-slug: got %d, body: %s", resp.Code, resp.Body.String())
	}
	var wf2 struct{ Slug string `json:"slug"` }
	json.Unmarshal(resp.Body.Bytes(), &wf2)
	if wf2.Slug != "bug-report-triage" {
		t.Errorf("auto-slug: got %q, want %q", wf2.Slug, "bug-report-triage")
	}

	// ── Delete stage ────────────────────────

	resp = h.request("DELETE", "/api/v1/workflows/"+wf.ID+"/stages/"+stage1.ID, h.adminToken, nil)
	if resp.Code != http.StatusOK {
		t.Fatalf("Delete stage: got %d", resp.Code)
	}

	// ── Delete workflow (cascades) ──────────

	resp = h.request("DELETE", "/api/v1/workflows/"+wf.ID, h.adminToken, nil)
	if resp.Code != http.StatusOK {
		t.Fatalf("Delete workflow: got %d", resp.Code)
	}

	// Confirm gone
	resp = h.request("GET", "/api/v1/workflows/"+wf.ID, h.adminToken, nil)
	if resp.Code != http.StatusNotFound {
		t.Errorf("After delete: got %d, want 404", resp.Code)
	}
}

// TestWorkflowValidation tests input validation on workflow endpoints.
func TestWorkflowValidation(t *testing.T) {
	h := setupWorkflowHarness(t)

	// Missing name
	resp := h.request("POST", "/api/v1/workflows", h.adminToken, map[string]interface{}{
		"slug": "test",
	})
	if resp.Code != http.StatusBadRequest {
		t.Errorf("Missing name: got %d, want 400", resp.Code)
	}

	// Invalid slug
	resp = h.request("POST", "/api/v1/workflows", h.adminToken, map[string]interface{}{
		"name": "Test",
		"slug": "A",
	})
	if resp.Code != http.StatusBadRequest {
		t.Errorf("Short slug: got %d, want 400", resp.Code)
	}

	// Invalid entry_mode
	resp = h.request("POST", "/api/v1/workflows", h.adminToken, map[string]interface{}{
		"name":       "Test",
		"entry_mode": "invalid",
	})
	if resp.Code != http.StatusBadRequest {
		t.Errorf("Bad entry_mode: got %d, want 400", resp.Code)
	}

	// Invalid history_mode on stage
	resp = h.request("POST", "/api/v1/workflows", h.adminToken, map[string]interface{}{
		"name": "For Stage Test",
	})
	var stageWf struct{ ID string `json:"id"` }
	json.Unmarshal(resp.Body.Bytes(), &stageWf)

	resp = h.request("POST", "/api/v1/workflows/"+stageWf.ID+"/stages", h.adminToken, map[string]interface{}{
		"name":         "Bad Mode",
		"history_mode": "invalid",
	})
	if resp.Code != http.StatusBadRequest {
		t.Errorf("Bad history_mode: got %d, want 400", resp.Code)
	}
}

// ── Workflow Test Harness ───────────────────

type workflowHarness struct {
	*testHarness
	adminToken string
	adminID    string
}

func setupWorkflowHarness(t *testing.T) *workflowHarness {
	t.Helper()
	database.RequireTestDB(t)
	database.TruncateAll(t)

	cfg := &config.Config{
		JWTSecret: testJWTSecret,
		BasePath:  "",
	}

	var stores store.Stores
	if database.IsSQLite() {
		stores = sqlite.NewStores(database.TestDB)
	} else {
		stores = postgres.NewStores(database.TestDB)
	}
	userCache := middleware.NewUserStatusCache()

	r := gin.New()
	api := r.Group("/api/v1")

	// Auth (for token generation)
	auth := NewAuthHandler(cfg, stores, nil, authpkg.NewBuiltinProvider())
	api.POST("/auth/login", auth.Login)
	api.POST("/auth/register", auth.Register)

	protected := api.Group("")
	protected.Use(middleware.Auth(cfg, stores.Users, userCache))

	// Workflow CRUD
	wfH := NewWorkflowHandler(stores)
	protected.GET("/workflows", wfH.List)
	protected.POST("/workflows", wfH.Create)
	protected.GET("/workflows/:id", wfH.Get)
	protected.PATCH("/workflows/:id", wfH.Update)
	protected.DELETE("/workflows/:id", wfH.Delete)
	protected.GET("/workflows/:id/stages", wfH.ListStages)
	protected.POST("/workflows/:id/stages", wfH.CreateStage)
	protected.PUT("/workflows/:id/stages/:sid", wfH.UpdateStage)
	protected.DELETE("/workflows/:id/stages/:sid", wfH.DeleteStage)
	protected.PATCH("/workflows/:id/stages/reorder", wfH.ReorderStages)
	protected.POST("/workflows/:id/publish", wfH.Publish)
	protected.GET("/workflows/:id/versions/:version", wfH.GetVersion)

	// Workflow instances
	wfInstH := NewWorkflowInstanceHandler(stores, nil, nil, nil)
	protected.POST("/workflows/:id/start", wfInstH.Start)
	protected.GET("/channels/:id/workflow/status", wfInstH.GetStatus)
	protected.POST("/channels/:id/workflow/advance", wfInstH.Advance)
	protected.POST("/channels/:id/workflow/reject", wfInstH.Reject)

	// Channels (needed for workflow instance creation)
	channels := NewChannelHandler(stores)
	protected.GET("/channels", channels.ListChannels)
	protected.POST("/channels", channels.CreateChannel)
	protected.GET("/channels/:id", channels.GetChannel)

	// Seed an admin user (handle + auth_source required since v0.24.0)
	adminID := seedInsertReturningID(t,
		`INSERT INTO users (username, email, password_hash, role, handle, auth_source) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id`,
		"wf-admin", "wf-admin@test.com", "$2a$10$test", "admin", "wf-admin", "builtin",
	)

	token := makeToken(adminID, "wf-admin@test.com", "admin")

	return &workflowHarness{
		testHarness: &testHarness{router: r, t: t},
		adminToken:  token,
		adminID:     adminID,
	}
}
