package reports

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

	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/shopspring/decimal"
)

type mockDetailedStore struct {
	fakeStore
	salesByCust     []SalesByCustomerRow
	salesByProd     []SalesByProductRow
	purchasesBySup  []PurchasesBySupplierRow
	purchasesByProd []PurchasesByProductRow
	receivAging     []AgingRow
	payablesAging   []AgingRow
	stockVal        []StockValuationRow
	stockMov        []StockMovementRow
	stockCard       []StockCardRow
}

func (m *mockDetailedStore) SalesByCustomer(ctx context.Context, f BreakdownFilter) ([]SalesByCustomerRow, int, error) {
	return m.salesByCust, len(m.salesByCust), nil
}

func (m *mockDetailedStore) SalesByProduct(ctx context.Context, f BreakdownFilter) ([]SalesByProductRow, int, error) {
	return m.salesByProd, len(m.salesByProd), nil
}

func (m *mockDetailedStore) PurchasesBySupplier(ctx context.Context, f BreakdownFilter) ([]PurchasesBySupplierRow, int, error) {
	return m.purchasesBySup, len(m.purchasesBySup), nil
}

func (m *mockDetailedStore) PurchasesByProduct(ctx context.Context, f BreakdownFilter) ([]PurchasesByProductRow, int, error) {
	return m.purchasesByProd, len(m.purchasesByProd), nil
}

func (m *mockDetailedStore) ReceivablesAging(ctx context.Context, f AgingFilter) ([]AgingRow, int, error) {
	return m.receivAging, len(m.receivAging), nil
}

func (m *mockDetailedStore) PayablesAging(ctx context.Context, f AgingFilter) ([]AgingRow, int, error) {
	return m.payablesAging, len(m.payablesAging), nil
}

func (m *mockDetailedStore) StockValuation(ctx context.Context, f StockValuationFilter) ([]StockValuationRow, int, error) {
	return m.stockVal, len(m.stockVal), nil
}

func (m *mockDetailedStore) StockMovements(ctx context.Context, f StockMovementsFilter) ([]StockMovementRow, int, error) {
	return m.stockMov, len(m.stockMov), nil
}

func (m *mockDetailedStore) StockCard(ctx context.Context, f StockCardFilter) ([]StockCardRow, error) {
	return m.stockCard, nil
}

func TestDetailedReports_Endpoints(t *testing.T) {
	mock := &mockDetailedStore{
		salesByCust: []SalesByCustomerRow{
			{
				CustomerCode: "CUST001",
				CustomerName: "PT Budi Jaya",
				Orders:       3,
				Qty:          decimal.NewFromInt(50),
				Omset:        decimal.NewFromInt(5000000),
				Discount:     decimal.NewFromInt(100000),
				HPP:          decimal.NewFromInt(3500000),
				Profit:       decimal.NewFromInt(1500000),
			},
		},
		salesByProd: []SalesByProductRow{
			{
				ProductCode: "PROD001",
				ProductName: "Minyak Goreng 2L",
				Qty:         decimal.NewFromInt(100),
				Omset:       decimal.NewFromInt(3400000),
				ReturQty:    decimal.NewFromInt(2),
				ReturAmount: decimal.NewFromInt(68000),
			},
		},
		purchasesBySup: []PurchasesBySupplierRow{
			{
				SupplierName: "PT Wings Surya",
				Orders:       2,
				Qty:          decimal.NewFromInt(200),
				Total:        decimal.NewFromInt(10000000),
				Paid:         decimal.NewFromInt(6000000),
				Remaining:    decimal.NewFromInt(4000000),
			},
		},
		purchasesByProd: []PurchasesByProductRow{
			{
				ProductCode: "PROD002",
				ProductName: "Beras 5kg",
				Qty:         decimal.NewFromInt(50),
				Received:    decimal.NewFromInt(50),
				Total:       decimal.NewFromInt(3500000),
			},
		},
		receivAging: []AgingRow{
			{
				Number:    "INV/2026/001",
				Party:     "Toko Sumber Rezeki",
				BillDate:  "2026-08-01",
				AgeDays:   38,
				Bucket:    "31_60",
				Total:     decimal.NewFromInt(2500000),
				Remaining: decimal.NewFromInt(1500000),
			},
		},
		payablesAging: []AgingRow{
			{
				Number:    "PO/2026/001",
				Party:     "PT Indofood",
				BillDate:  "2026-07-15",
				AgeDays:   55,
				Bucket:    "31_60",
				Total:     decimal.NewFromInt(8000000),
				Remaining: decimal.NewFromInt(4000000),
			},
		},
		stockVal: []StockValuationRow{
			{
				ProductCode: "PROD001",
				ProductName: "Minyak Goreng 2L",
				WarehouseID: 1,
				Qty:         decimal.NewFromInt(150),
				AvgCost:     decimal.NewFromInt(30000),
				Value:       decimal.NewFromInt(4500000),
				SellPrice:   decimal.NewFromInt(34000),
			},
		},
		stockMov: []StockMovementRow{
			{
				Date:        "2026-09-01",
				Number:      "MOV001",
				Type:        "stock_in",
				ProductCode: "PROD001",
				ProductName: "Minyak Goreng 2L",
				Qty:         decimal.NewFromInt(50),
				After:       decimal.NewFromInt(150),
				Reference:   "PO #12",
			},
		},
		stockCard: []StockCardRow{
			{
				Date:      "2026-09-01",
				Number:    "MOV001",
				Type:      "stock_in",
				In:        decimal.NewFromInt(50),
				Out:       decimal.Zero,
				Balance:   decimal.NewFromInt(150),
				Reference: "PO #12",
			},
		},
	}

	svc := NewService(mock, loc())
	h := NewHandler(svc)

	tests := []struct {
		name       string
		path       string
		handler    http.HandlerFunc
		isCard     bool
		wantKey    string
		wantValStr string
	}{
		{
			name:       "SalesByCustomer",
			path:       "/reports/sales-by-customer?page=1&per_page=10",
			handler:    h.SalesByCustomer,
			wantKey:    "customer_code",
			wantValStr: "CUST001",
		},
		{
			name:       "SalesByProduct",
			path:       "/reports/sales-by-product?page=1&per_page=10",
			handler:    h.SalesByProduct,
			wantKey:    "product_code",
			wantValStr: "PROD001",
		},
		{
			name:       "PurchasesBySupplier",
			path:       "/reports/purchases-by-supplier?page=1&per_page=10",
			handler:    h.PurchasesBySupplier,
			wantKey:    "supplier_name",
			wantValStr: "PT Wings Surya",
		},
		{
			name:       "PurchasesByProduct",
			path:       "/reports/purchases-by-product?page=1&per_page=10",
			handler:    h.PurchasesByProduct,
			wantKey:    "product_code",
			wantValStr: "PROD002",
		},
		{
			name:       "ReceivablesAging",
			path:       "/reports/receivables-aging?page=1&per_page=10",
			handler:    h.ReceivablesAging,
			wantKey:    "bucket",
			wantValStr: "31_60",
		},
		{
			name:       "PayablesAging",
			path:       "/reports/payables-aging?page=1&per_page=10",
			handler:    h.PayablesAging,
			wantKey:    "bucket",
			wantValStr: "31_60",
		},
		{
			name:       "StockValuation",
			path:       "/reports/stock-valuation?page=1&per_page=10",
			handler:    h.StockValuation,
			wantKey:    "product_code",
			wantValStr: "PROD001",
		},
		{
			name:       "StockMovements",
			path:       "/reports/stock-movements?page=1&per_page=10",
			handler:    h.StockMovements,
			wantKey:    "reference",
			wantValStr: "PO #12",
		},
		{
			name:       "StockCard",
			path:       "/reports/stock-card?product_id=1&warehouse_id=1",
			handler:    h.StockCard,
			isCard:     true,
			wantKey:    "reference",
			wantValStr: "PO #12",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			req := httptest.NewRequest(http.MethodGet, tc.path, nil)
			rr := httptest.NewRecorder()
			tc.handler(rr, req)

			if rr.Code != http.StatusOK {
				t.Fatalf("status = %d, want 200. Body: %s", rr.Code, rr.Body.String())
			}

			var body struct {
				Data []map[string]any `json:"data"`
				Meta map[string]any   `json:"meta"`
			}
			if err := json.Unmarshal(rr.Body.Bytes(), &body); err != nil {
				t.Fatalf("failed to decode JSON: %v", err)
			}

			if len(body.Data) == 0 {
				t.Fatalf("expected data array to be non-empty")
			}
			if val, ok := body.Data[0][tc.wantKey]; !ok || val != tc.wantValStr {
				t.Errorf("data[0][%s] = %v, want %s", tc.wantKey, val, tc.wantValStr)
			}

			if !tc.isCard {
				if pag, ok := body.Meta["pagination"].(map[string]any); !ok || pag["total"] != float64(1) {
					t.Errorf("meta.pagination.total = %v, want 1", body.Meta["pagination"])
				}
			}
		})
	}
}

func TestStockCard_EmptyProductID(t *testing.T) {
	svc := NewService(&mockDetailedStore{}, loc())
	h := NewHandler(svc)
	req := httptest.NewRequest(http.MethodGet, "/reports/stock-card", nil)
	rr := httptest.NewRecorder()
	h.StockCard(rr, req)

	if rr.Code != http.StatusOK {
		t.Fatalf("status = %d, want 200", rr.Code)
	}

	var body struct {
		Data []map[string]any `json:"data"`
	}
	if err := json.Unmarshal(rr.Body.Bytes(), &body); err != nil {
		t.Fatalf("failed to decode JSON: %v", err)
	}
	if len(body.Data) != 0 {
		t.Errorf("expected empty data array for empty product_id")
	}
}

func (f *mockDetailedStore) Pool() *pgxpool.Pool { return nil }
