package reportproxy

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

	"github.com/TaruLab/keluargamart-backend/internal/auth"
	"github.com/TaruLab/keluargamart-backend/internal/distribution"
	"github.com/TaruLab/keluargamart-backend/internal/finance"
	"github.com/TaruLab/keluargamart-backend/internal/purchasing"
	"github.com/TaruLab/keluargamart-backend/internal/reports/templates"
	"github.com/shopspring/decimal"
)

type mockTplResolver struct {
	tpl *templates.ReportTemplate
	err error
}

func (m *mockTplResolver) GetActiveTemplate(ctx context.Context, code string) (*templates.ReportTemplate, error) {
	return m.tpl, m.err
}

func TestDataShapers(t *testing.T) {
	t.Run("ShapeInvoiceData", func(t *testing.T) {
		inv := &finance.Invoice{
			ID:              10,
			InvoiceNumber:   "INV-TEST-01",
			InvoiceDate:     "13/09/2026",
			DueDate:         "20/09/2026",
			Status:          "confirmed",
			CustomerID:      5,
			CustomerName:    "Toko Berkah",
			Subtotal:        decimal.NewFromInt(100000),
			TaxAmount:       decimal.NewFromInt(11000),
			TotalAmount:     decimal.NewFromInt(111000),
			PaidAmount:      decimal.NewFromInt(50000),
			RemainingAmount: decimal.NewFromInt(61000),
			Items: []finance.InvoiceItem{
				{
					ProductCode:    "PRD-01",
					ProductName:    "Beras 5kg",
					Quantity:       decimal.NewFromInt(2),
					UnitPrice:      decimal.NewFromInt(50000),
					DiscountAmount: decimal.NewFromInt(0),
					Subtotal:       decimal.NewFromInt(100000),
				},
			},
		}

		res := ShapeInvoiceData(inv, "Tester Admin")
		if res["invoice"].(map[string]any)["number"] != "INV-TEST-01" {
			t.Errorf("expected number INV-TEST-01, got %v", res["invoice"])
		}
		totals := res["totals"].(map[string]any)
		if totals["grand_total"] != int64(111000) {
			t.Errorf("expected grand_total 111000, got %v", totals["grand_total"])
		}
	})

	t.Run("ShapePOData", func(t *testing.T) {
		expectedDate := "18/09/2026"
		po := &purchasing.PurchaseOrder{
			ID:           20,
			PoNumber:     "PO-TEST-01",
			OrderDate:    "13/09/2026",
			ExpectedDate: &expectedDate,
			Status:       "approved",
			SupplierName: "Supplier Utama",
			Subtotal:     decimal.NewFromInt(200000),
			TaxAmount:    decimal.NewFromInt(22000),
			TotalAmount:  decimal.NewFromInt(222000),
			Items: []purchasing.POItem{
				{
					ProductCode: "P-01",
					ProductName: "Minyak 2L",
					Quantity:    decimal.NewFromInt(10),
					UnitSymbol:  "Pcs",
					UnitPrice:   decimal.NewFromInt(20000),
					Subtotal:    decimal.NewFromInt(200000),
				},
			},
		}

		res := ShapePOData(po, "Manager PO")
		poMap := res["po"].(map[string]any)
		if poMap["number"] != "PO-TEST-01" {
			t.Errorf("expected PO-TEST-01, got %v", poMap["number"])
		}
		supMap := res["supplier"].(map[string]any)
		if supMap["name"] != "Supplier Utama" {
			t.Errorf("expected Supplier Utama, got %v", supMap["name"])
		}
	})

	t.Run("ShapeDOData", func(t *testing.T) {
		addr := "Jl. Anggrek No. 1"
		do := &distribution.DeliveryOrder{
			ID:              30,
			DoNumber:        "DO-TEST-01",
			DoDate:          "13/09/2026",
			Status:          "in_transit",
			CustomerName:    "PT Sukses",
			ShippingAddress: &addr,
			Items: []distribution.DeliveryOrderItem{
				{
					ProductCode:       "P-01",
					ProductName:       "Gula 1kg",
					RequestedQuantity: decimal.NewFromInt(5),
					ShippedQuantity:   decimal.NewFromInt(5),
					DeliveredQuantity: decimal.NewFromInt(0),
				},
			},
		}

		res := ShapeDOData(do, "Dispatcher Budi")
		doMap := res["delivery_order"].(map[string]any)
		if doMap["number"] != "DO-TEST-01" {
			t.Errorf("expected DO-TEST-01, got %v", doMap["number"])
		}
		if totals, ok := res["totals"].(map[string]any); !ok || totals["grand_total"] == nil {
			t.Errorf("expected totals map with grand_total, got %v", res["totals"])
		}
	})

	t.Run("ShapeGRData", func(t *testing.T) {
		gr := &purchasing.GoodsReceipt{
			ID:            40,
			GrNumber:      "GR-TEST-01",
			PONumber:      "PO-TEST-01",
			ReceivedDate:  "13/09/2026",
			SupplierName:  "Supplier Indah",
			WarehouseName: "Gudang Utama",
			Items: []purchasing.GRItem{
				{
					ProductCode:      "P-01",
					ProductName:      "Tepung Terigu",
					QuantityOrdered:  decimal.NewFromInt(10),
					QuantityReceived: decimal.NewFromInt(10),
				},
			},
		}

		res := ShapeGRData(gr, "Gudang Staff")
		recMap := res["receipt"].(map[string]any)
		if recMap["number"] != "GR-TEST-01" {
			t.Errorf("expected GR-TEST-01, got %v", recMap["number"])
		}
	})
}

func TestResolveInvoiceData_NoPassthrough(t *testing.T) {
	h := &Handler{}
	// Forged document with caller-controlled totals and no invoice_id must not
	// pass through: resolver falls back to an explicit INV-PREVIEW contract.
	forged := map[string]any{
		"invoice":       map[string]any{"number": "INV-FORGE-1"},
		"total_amount":  float64(999999999),
		"grand_total":   float64(999999999),
		"customer_name": "Attacker",
	}
	res, ok := h.resolveInvoiceData(context.Background(), map[string][]string{}, forged, "Auditor", 0)
	if !ok {
		t.Fatalf("expected preview path to succeed")
	}
	m, ok2 := res.(map[string]any)
	if !ok2 {
		t.Fatalf("expected map[string]any, got %T", res)
	}
	if got := m["invoice"].(map[string]any)["number"]; got != "INV-PREVIEW" {
		t.Errorf("expected INV-PREVIEW fallback, got %v", got)
	}
	if got := m["totals"].(map[string]any)["grand_total"]; got != int64(0) {
		t.Errorf("expected zeroed grand_total, got %v", got)
	}
}

func TestRender_NextReportPath(t *testing.T) {
	// Mock NextReport engine HTTP server
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/render" {
			http.NotFound(w, r)
			return
		}
		var payload map[string]any
		_ = json.NewDecoder(r.Body).Decode(&payload)
		format, _ := payload["format"].(string)
		if format == "pdf" {
			w.Header().Set("Content-Type", "application/pdf")
			w.WriteHeader(http.StatusOK)
			_, _ = w.Write([]byte("%PDF-1.4 mock"))
			return
		}
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("<html><body>Mock Report</body></html>"))
	}))
	defer ts.Close()

	handler := NewHandler(ts.URL)


	mockTpl := &mockTplResolver{
		tpl: &templates.ReportTemplate{
			ID:           1,
			ReportCode:   "po-print",
			Version:      1,
			Status:       "active",
			TemplateData: json.RawMessage(`{"type":"report","page":{"size":"A4"}}`),
		},
	}
	handler.SetTemplateResolver(mockTpl)

	// Context with test admin user
	u := &auth.User{
		ID:   1,
		Name: "Test Buyer",
		Role: auth.RoleAdmin,
	}
	ctx := auth.ToContext(context.Background(), u, 1)

	req := httptest.NewRequest(http.MethodPost, "/reports/render?template=po-print&format=pdf", nil)
	req = req.WithContext(ctx)
	w := httptest.NewRecorder()

	handler.Render(w, req)
	resp := w.Result()
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200 OK, got %d", resp.StatusCode)
	}
	if ct := resp.Header.Get("Content-Type"); ct != "application/pdf" {
		t.Errorf("expected Content-Type application/pdf, got %s", ct)
	}
	body := w.Body.String()
	if body != "%PDF-1.4 mock" {
		t.Errorf("unexpected body: %s", body)
	}
}

func TestResolveAnalyticalData(t *testing.T) {
	h := &Handler{}
	raw := map[string]any{
		"rows": []any{
			map[string]any{"product_code": "BRS-01", "qty": 10},
		},
		"total": map[string]any{"qty": 10},
		"filters": map[string]any{
			"date_from": "01/09/2026",
		},
	}
	res := h.resolveAnalyticalData(context.Background(), "sales-by-product", map[string][]string{"date_to": {"13/09/2026"}}, raw, "Auditor")
	m, ok := res.(map[string]any)
	if !ok {
		t.Fatalf("expected map[string]any, got %T", res)
	}
	filters := m["filters"].(map[string]any)
	if filters["printed_by"] != "Auditor" {
		t.Errorf("expected printed_by Auditor, got %v", filters["printed_by"])
	}
	if filters["date_to"] != "13/09/2026" {
		t.Errorf("expected date_to 13/09/2026, got %v", filters["date_to"])
	}
	if len(m["rows"].([]any)) != 1 {
		t.Errorf("expected 1 row, got %v", len(m["rows"].([]any)))
	}
}

func TestResolveSlipData(t *testing.T) {
	h := &Handler{}

	t.Run("voucher-print", func(t *testing.T) {
		raw := map[string]any{
			"voucher_number": "PV-202609-001",
			"beneficiary":    "PT Sumber Makmur",
			"amount":         float64(500000),
		}
		res := h.resolveSlipData(context.Background(), "voucher-print", nil, raw, "Kasir Dewi")
		m := res.(map[string]any)
		v := m["voucher"].(map[string]any)
		if v["number"] != "PV-202609-001" {
			t.Errorf("expected PV-202609-001, got %v", v["number"])
		}
		if v["payee"] != "PT Sumber Makmur" {
			t.Errorf("expected PT Sumber Makmur, got %v", v["payee"])
		}
	})

	t.Run("lhpp-print", func(t *testing.T) {
		raw := map[string]any{
			"lhpp_number": "LHPP-202609-001",
			"driver_name": "Supir Rudi",
		}
		res := h.resolveSlipData(context.Background(), "lhpp-print", nil, raw, "Admin")
		m := res.(map[string]any)
		lhpp := m["lhpp"].(map[string]any)
		driver := m["driver"].(map[string]any)
		if lhpp["number"] != "LHPP-202609-001" {
			t.Errorf("expected LHPP-202609-001, got %v", lhpp["number"])
		}
		if driver["name"] != "Supir Rudi" {
			t.Errorf("expected Supir Rudi, got %v", driver["name"])
		}
	})

	t.Run("pos-receipt", func(t *testing.T) {
		raw := map[string]any{
			"transaction_number": "TRX-20260913-0001",
		}
		res := h.resolveSlipData(context.Background(), "pos-receipt", nil, raw, "Kasir 1")
		m := res.(map[string]any)
		receipt := m["receipt"].(map[string]any)
		if receipt["number"] != "TRX-20260913-0001" {
			t.Errorf("expected TRX-20260913-0001, got %v", receipt["number"])
		}
		if receipt["cashier"] != "Kasir 1" {
			t.Errorf("expected cashier Kasir 1, got %v", receipt["cashier"])
		}
	})

	t.Run("shift-print", func(t *testing.T) {
		// POS summary shape: decimals as strings, timestamps RFC3339, keys
		// renamed by the proxy (opening_cash → initial_cash, variance →
		// cash_difference, opened_at → start_time).
		raw := map[string]any{
			"register_name":     "POS-01",
			"opening_cash":      "5000000",
			"cash_sales":        "10000000",
			"non_cash_sales":    "2500000",
			"transaction_count": float64(12),
			"actual_cash":       "14999000",
			"variance":          -1000.0,
			"opened_at":         "2026-09-28T08:05:00Z",
			"closed_at":         "2026-09-28T16:02:00Z",
		}
		res := h.resolveSlipData(context.Background(), "shift-print", nil, raw, "Kasir 1")
		m := res.(map[string]any)
		shift := m["shift"].(map[string]any)
		if shift["register_name"] != "POS-01" {
			t.Errorf("expected POS-01, got %v", shift["register_name"])
		}
		if shift["cashier_name"] != "Kasir 1" {
			t.Errorf("expected Kasir 1, got %v", shift["cashier_name"])
		}
		if shift["initial_cash"] != int64(5000000) {
			t.Errorf("expected initial_cash 5000000, got %v", shift["initial_cash"])
		}
		if shift["cash_sales"] != int64(10000000) {
			t.Errorf("expected cash_sales 10000000, got %v", shift["cash_sales"])
		}
		if shift["non_cash_sales"] != int64(2500000) {
			t.Errorf("expected non_cash_sales 2500000, got %v", shift["non_cash_sales"])
		}
		if shift["transaction_count"] != int64(12) {
			t.Errorf("expected transaction_count 12, got %v", shift["transaction_count"])
		}
		if shift["actual_cash"] != int64(14999000) {
			t.Errorf("expected actual_cash 14999000, got %v", shift["actual_cash"])
		}
		if shift["cash_difference"] != int64(-1000) {
			t.Errorf("expected cash_difference -1000, got %v", shift["cash_difference"])
		}
		if shift["start_time"] != "28/09/2026 08:05" {
			t.Errorf("expected start_time 28/09/2026 08:05, got %v", shift["start_time"])
		}
		if shift["end_time"] != "28/09/2026 16:02" {
			t.Errorf("expected end_time 28/09/2026 16:02, got %v", shift["end_time"])
		}
	})

	t.Run("shift-print legacy keys", func(t *testing.T) {
		// Callers posting the old field names keep working.
		raw := map[string]any{
			"initial_cash": 500000,
		}
		res := h.resolveSlipData(context.Background(), "shift-print", nil, raw, "Kasir 1")
		m := res.(map[string]any)
		shift := m["shift"].(map[string]any)
		if shift["initial_cash"] != int64(500000) {
			t.Errorf("expected initial_cash 500000, got %v", shift["initial_cash"])
		}
	})
}

