package test

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strconv"
	"testing"

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

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

func newBudgetServer(t *testing.T) *httptest.Server {
	t.Helper()
	db := testutil.NewTestDB(t)
	repo := database.NewBudgetRepo(db)
	h := handler.NewBudgetHandler(service.NewBudgetService(repo))

	mux := http.NewServeMux()
	mux.HandleFunc("POST /api/v1/budgets", h.Create)
	mux.HandleFunc("PUT /api/v1/budgets/{id}", h.Update)
	mux.HandleFunc("DELETE /api/v1/budgets/{id}", h.Delete)

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

func newBudgetHandler(t *testing.T) *handler.BudgetHandler {
	t.Helper()
	return handler.NewBudgetHandler(service.NewBudgetService(database.NewBudgetRepo(testutil.NewTestDB(t))))
}

func validBudget() map[string]any {
	return map[string]any{
		"fiscal_year":   2024,
		"fiscal_period": 1,
		"version":       "original", // adjust to match your BudgetVersion values
		"department_id": 1,
		"gl_account_id": 1,
		"amount":        5000.00,
		"currency":      "USD",
		"notes":         "",
		"created_by":    "test",
	}
}

// ── Create ────────────────────────────────────────────────────────────────────

func TestCreateBudget_OK(t *testing.T) {
	h := newBudgetHandler(t)

	w := testutil.Do(t, http.HandlerFunc(h.Create), http.MethodPost, "/", validBudget())
	testutil.AssertStatus(t, w, http.StatusCreated)

	var got model.Budget
	testutil.DecodeJSON(t, w, &got)
	if got.ID == 0 {
		t.Error("expected non-zero ID")
	}
	if got.Amount != 5000.00 {
		t.Errorf("amount: got %v, want 5000.00", got.Amount)
	}
}

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

func TestCreateBudget_MissingPeriod(t *testing.T) {
	h := newBudgetHandler(t)
	body := validBudget()
	delete(body, "fiscal_period")
	w := testutil.Do(t, http.HandlerFunc(h.Create), http.MethodPost, "/", body)
	testutil.AssertStatus(t, w, http.StatusBadRequest)
}

func TestCreateBudget_ZeroAmount(t *testing.T) {
	h := newBudgetHandler(t)
	body := validBudget()
	body["amount"] = 0
	w := testutil.Do(t, http.HandlerFunc(h.Create), http.MethodPost, "/", body)
	// Whether 0 is rejected or accepted depends on your business rule — adjust to match
	t.Logf("zero amount response: %d — verify against your handler", w.Code)
}

func TestCreateBudget_NegativeAmount(t *testing.T) {
	h := newBudgetHandler(t)
	body := validBudget()
	body["amount"] = -100
	w := testutil.Do(t, http.HandlerFunc(h.Create), http.MethodPost, "/", body)
	testutil.AssertStatus(t, w, http.StatusBadRequest)
}

// ── Update ────────────────────────────────────────────────────────────────────

func TestUpdateBudget_OK(t *testing.T) {
	srv := newBudgetServer(t)
	client := srv.Client()

	// Create first
	resp, err := client.Post(srv.URL+"/api/v1/budgets", "application/json",
		mustJSON(t, validBudget()))
	if err != nil {
		t.Fatal(err)
	}
	var created model.Budget
	json.NewDecoder(resp.Body).Decode(&created)
	resp.Body.Close()

	// Update amount
	updated := validBudget()
	updated["amount"] = 9999.99

	req, _ := http.NewRequest(http.MethodPut,
		fmt.Sprintf("%s/api/v1/budgets/%d", srv.URL, created.ID),
		mustJSON(t, updated))
	req.Header.Set("Content-Type", "application/json")
	resp2, err := client.Do(req)
	if err != nil {
		t.Fatal(err)
	}
	defer resp2.Body.Close()

	if resp2.StatusCode != http.StatusOK && resp2.StatusCode != http.StatusNoContent {
		t.Errorf("update: got %d, want 200 or 204", resp2.StatusCode)
	}

	if resp2.StatusCode == http.StatusOK {
		var got model.Budget
		json.NewDecoder(resp2.Body).Decode(&got)
		if got.Amount != 9999.99 {
			t.Errorf("updated amount: got %v, want 9999.99", got.Amount)
		}
	}
}

func TestUpdateBudget_NotFound(t *testing.T) {
	srv := newBudgetServer(t)

	req, _ := http.NewRequest(http.MethodPut, srv.URL+"/api/v1/budgets/9999",
		mustJSON(t, validBudget()))
	req.Header.Set("Content-Type", "application/json")
	resp, err := srv.Client().Do(req)
	if err != nil {
		t.Fatal(err)
	}
	resp.Body.Close()

	if resp.StatusCode != http.StatusNotFound && resp.StatusCode != http.StatusNoContent {
		t.Errorf("update non-existent: got %d, want 404 or 204", resp.StatusCode)
	}
}

func TestUpdateBudget_InvalidJSON(t *testing.T) {
	srv := newBudgetServer(t)

	// Create one first so the ID exists
	resp, _ := srv.Client().Post(srv.URL+"/api/v1/budgets", "application/json",
		mustJSON(t, validBudget()))
	var created model.Budget
	json.NewDecoder(resp.Body).Decode(&created)
	resp.Body.Close()

	req, _ := http.NewRequest(http.MethodPut,
		fmt.Sprintf("%s/api/v1/budgets/%d", srv.URL, created.ID),
		bytes.NewBufferString("not-json"))
	req.Header.Set("Content-Type", "application/json")
	resp2, err := srv.Client().Do(req)
	if err != nil {
		t.Fatal(err)
	}
	resp2.Body.Close()
	if resp2.StatusCode != http.StatusBadRequest {
		t.Errorf("invalid JSON update: got %d, want 400", resp2.StatusCode)
	}
}

// ── Delete ────────────────────────────────────────────────────────────────────

func TestDeleteBudget_OK(t *testing.T) {
	srv := newBudgetServer(t)
	client := srv.Client()

	resp, err := client.Post(srv.URL+"/api/v1/budgets", "application/json",
		mustJSON(t, validBudget()))
	if err != nil {
		t.Fatal(err)
	}
	var created model.Budget
	json.NewDecoder(resp.Body).Decode(&created)
	resp.Body.Close()

	req, _ := http.NewRequest(http.MethodDelete,
		srv.URL+"/api/v1/budgets/"+strconv.Itoa(created.ID), nil)
	resp2, err := client.Do(req)
	if err != nil {
		t.Fatal(err)
	}
	resp2.Body.Close()

	if resp2.StatusCode != http.StatusNoContent && resp2.StatusCode != http.StatusOK {
		t.Errorf("delete: got %d, want 200 or 204", resp2.StatusCode)
	}
}

func TestDeleteBudget_NotFound(t *testing.T) {
	srv := newBudgetServer(t)

	req, _ := http.NewRequest(http.MethodDelete, srv.URL+"/api/v1/budgets/9999", nil)
	resp, err := srv.Client().Do(req)
	if err != nil {
		t.Fatal(err)
	}
	resp.Body.Close()

	if resp.StatusCode != http.StatusNotFound && resp.StatusCode != http.StatusNoContent {
		t.Errorf("delete non-existent: got %d, want 404 or 204", resp.StatusCode)
	}
}

func TestDeleteBudget_DoubleDelete(t *testing.T) {
	srv := newBudgetServer(t)
	client := srv.Client()

	resp, _ := client.Post(srv.URL+"/api/v1/budgets", "application/json",
		mustJSON(t, validBudget()))
	var created model.Budget
	json.NewDecoder(resp.Body).Decode(&created)
	resp.Body.Close()

	url := srv.URL + "/api/v1/budgets/" + strconv.Itoa(created.ID)

	req1, _ := http.NewRequest(http.MethodDelete, url, nil)
	resp1, _ := client.Do(req1)
	resp1.Body.Close()

	// Second delete — should not panic, should return 404 or 204
	req2, _ := http.NewRequest(http.MethodDelete, url, nil)
	resp2, err := client.Do(req2)
	if err != nil {
		t.Fatal(err)
	}
	resp2.Body.Close()

	if resp2.StatusCode != http.StatusNotFound && resp2.StatusCode != http.StatusNoContent {
		t.Errorf("double delete: got %d, want 404 or 204", resp2.StatusCode)
	}
}
