package test

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

	"Engine/internal/database"
	"Engine/internal/handler"
	"Engine/internal/service"
	"Engine/tests/internal/testutil"
)

// ── wire helpers ──────────────────────────────────────────────────────────────

type testServer struct {
	srv *httptest.Server
	db  interface{ Close() error }
}

func newFullServer(t *testing.T) *httptest.Server {
	t.Helper()
	db := testutil.NewTestDB(t)

	actualsRepo := database.NewActualsRepo(db)
	actualsH := handler.NewActualsHandler(actualsRepo)

	budgetRepo := database.NewBudgetRepo(db)
	varianceH := handler.NewVarianceHandler(service.NewVarianceService(budgetRepo, actualsRepo))

	mux := http.NewServeMux()
	mux.HandleFunc("POST /api/v1/actuals/ingest", actualsH.Ingest)
	mux.HandleFunc("GET /api/v1/variance", varianceH.Report)
	mux.HandleFunc("GET /api/v1/variance/alerts", varianceH.Alerts)

	srv := httptest.NewServer(mux)
	t.Cleanup(srv.Close)
	return srv
}

func newActualsHandler(t *testing.T) *handler.ActualsHandler {
	t.Helper()
	return handler.NewActualsHandler(database.NewActualsRepo(testutil.NewTestDB(t)))
}

// validActual returns one well-formed actual record.
func validActual() map[string]any {
	return map[string]any{
		"department_id": 1,
		"gl_account_id": 1,
		"period":        "2024-01",
		"amount":        1234.56,
		"source":        "csv_import",
	}
}

// ── Actuals: Ingest ───────────────────────────────────────────────────────────

func TestIngestActuals_SingleRecord(t *testing.T) {
	h := newActualsHandler(t)

	w := testutil.Do(t, http.HandlerFunc(h.Ingest), http.MethodPost, "/",
		[]any{validActual()})

	testutil.AssertStatus(t, w, http.StatusCreated)
}

func TestIngestActuals_MultipleRecords(t *testing.T) {
	h := newActualsHandler(t)

	records := []any{
		map[string]any{"department_id": 1, "gl_account_id": 1, "period": "2024-01", "amount": 100.00, "source": "csv"},
		map[string]any{"department_id": 1, "gl_account_id": 2, "period": "2024-01", "amount": 200.00, "source": "csv"},
		map[string]any{"department_id": 2, "gl_account_id": 1, "period": "2024-01", "amount": 300.00, "source": "csv"},
	}

	w := testutil.Do(t, http.HandlerFunc(h.Ingest), http.MethodPost, "/", records)
	testutil.AssertStatus(t, w, http.StatusCreated)
}

func TestIngestActuals_EmptyList(t *testing.T) {
	h := newActualsHandler(t)

	w := testutil.Do(t, http.HandlerFunc(h.Ingest), http.MethodPost, "/", []any{})
	// Depending on your handler: 201 with 0 rows ingested, or 400
	t.Logf("empty ingest: %d — verify against your handler", w.Code)
}

func TestIngestActuals_InvalidJSON(t *testing.T) {
	h := newActualsHandler(t)
	w := testutil.Do(t, http.HandlerFunc(h.Ingest), http.MethodPost, "/", nil)
	testutil.AssertStatus(t, w, http.StatusBadRequest)
}

func TestIngestActuals_MissingPeriod(t *testing.T) {
	h := newActualsHandler(t)
	record := validActual()
	delete(record, "period")
	w := testutil.Do(t, http.HandlerFunc(h.Ingest), http.MethodPost, "/", []any{record})
	testutil.AssertStatus(t, w, http.StatusBadRequest)
}

func TestIngestActuals_NegativeAmount(t *testing.T) {
	h := newActualsHandler(t)
	record := validActual()
	record["amount"] = -500.00
	w := testutil.Do(t, http.HandlerFunc(h.Ingest), http.MethodPost, "/", []any{record})
	// Adjust to match your validation rule
	testutil.AssertStatus(t, w, http.StatusBadRequest)
}

func TestIngestActuals_Idempotent(t *testing.T) {
	h := newActualsHandler(t)
	fn := http.HandlerFunc(h.Ingest)

	testutil.Do(t, fn, http.MethodPost, "/", []any{validActual()})
	// Ingesting the same record twice should not panic or 500
	w := testutil.Do(t, fn, http.MethodPost, "/", []any{validActual()})
	if w.Code >= 500 {
		t.Errorf("duplicate ingest returned %d, expected non-5xx", w.Code)
	}
}

// ── Variance: Report ──────────────────────────────────────────────────────────

func TestVarianceReport_Empty(t *testing.T) {
	srv := newFullServer(t)

	resp, err := srv.Client().Get(srv.URL + "/api/v1/variance?period=2024-01")
	if err != nil {
		t.Fatal(err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		t.Errorf("variance report: got %d, want 200", resp.StatusCode)
	}
}

func TestVarianceReport_WithData(t *testing.T) {
	srv := newFullServer(t)
	client := srv.Client()

	// Seed a budget
	budgetPayload := map[string]any{
		"department_id": 1, "gl_account_id": 1, "period": "2024-01", "amount": 10000.00,
	}
	resp, _ := client.Post(srv.URL+"/api/v1/budgets", "application/json",
		mustJSON(t, budgetPayload))
	resp.Body.Close()

	// Ingest an actual — under budget
	actualPayload := []any{
		map[string]any{"department_id": 1, "gl_account_id": 1, "period": "2024-01", "amount": 7500.00, "source": "test"},
	}
	resp2, _ := client.Post(srv.URL+"/api/v1/actuals/ingest", "application/json",
		mustJSON(t, actualPayload))
	resp2.Body.Close()

	// Get variance report
	resp3, err := client.Get(srv.URL + "/api/v1/variance?period=2024-01")
	if err != nil {
		t.Fatal(err)
	}
	defer resp3.Body.Close()

	if resp3.StatusCode != http.StatusOK {
		t.Errorf("variance report: got %d, want 200", resp3.StatusCode)
	}
}

func TestVarianceReport_MissingPeriodParam(t *testing.T) {
	srv := newFullServer(t)

	resp, err := srv.Client().Get(srv.URL + "/api/v1/variance")
	if err != nil {
		t.Fatal(err)
	}
	resp.Body.Close()

	// Should return 400 if period is required, or 200 with all periods
	t.Logf("variance without period param: %d — verify against your handler", resp.StatusCode)
}

// ── Variance: Alerts ──────────────────────────────────────────────────────────

func TestVarianceAlerts_NoAlerts(t *testing.T) {
	srv := newFullServer(t)

	resp, err := srv.Client().Get(srv.URL + "/api/v1/variance/alerts?period=2024-01")
	if err != nil {
		t.Fatal(err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		t.Errorf("alerts: got %d, want 200", resp.StatusCode)
	}
}

func TestVarianceAlerts_OverBudgetTriggersAlert(t *testing.T) {
	srv := newFullServer(t)
	client := srv.Client()

	// Budget: 1000
	budgetPayload := map[string]any{
		"department_id": 1, "gl_account_id": 1, "period": "2024-02", "amount": 1000.00,
	}
	resp, _ := client.Post(srv.URL+"/api/v1/budgets", "application/json",
		mustJSON(t, budgetPayload))
	resp.Body.Close()

	// Actual: 1500 — 50% over budget
	actualPayload := []any{
		map[string]any{"department_id": 1, "gl_account_id": 1, "period": "2024-02", "amount": 1500.00, "source": "test"},
	}
	resp2, _ := client.Post(srv.URL+"/api/v1/actuals/ingest", "application/json",
		mustJSON(t, actualPayload))
	resp2.Body.Close()

	resp3, err := client.Get(srv.URL + "/api/v1/variance/alerts?period=2024-02")
	if err != nil {
		t.Fatal(err)
	}
	defer resp3.Body.Close()

	if resp3.StatusCode != http.StatusOK {
		t.Errorf("alerts: got %d, want 200", resp3.StatusCode)
	}

	// Decode whatever shape your alerts response is
	var alerts any
	if err := json.NewDecoder(resp3.Body).Decode(&alerts); err != nil {
		t.Fatalf("decode alerts: %v", err)
	}
	t.Logf("alerts payload: %+v", alerts)
}

func TestVarianceAlerts_UnderBudgetNoAlert(t *testing.T) {
	srv := newFullServer(t)
	client := srv.Client()

	// Budget: 5000, Actual: 1000 — well under, no alert expected
	budgetPayload := map[string]any{
		"department_id": 2, "gl_account_id": 2, "period": "2024-03", "amount": 5000.00,
	}
	resp, _ := client.Post(srv.URL+"/api/v1/budgets", "application/json",
		mustJSON(t, budgetPayload))
	resp.Body.Close()

	actualPayload := []any{
		map[string]any{"department_id": 2, "gl_account_id": 2, "period": "2024-03", "amount": 1000.00, "source": "test"},
	}
	resp2, _ := client.Post(srv.URL+"/api/v1/actuals/ingest", "application/json",
		mustJSON(t, actualPayload))
	resp2.Body.Close()

	resp3, err := client.Get(srv.URL + "/api/v1/variance/alerts?period=2024-03")
	if err != nil {
		t.Fatal(err)
	}
	defer resp3.Body.Close()

	if resp3.StatusCode != http.StatusOK {
		t.Errorf("alerts: got %d, want 200", resp3.StatusCode)
	}
}
