package main

import (
	"context"
	"errors"
	"strings"
	"testing"
	"time"

	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	transferSvc "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/services"
)

// ── Fake executor ─────────────────────────────────────────────────────────────

type fakeSystemTransferExecutor struct {
	createRequests     []*transferSvc.SystemTransferRequest
	createResponses    []*entity.StockTransfer
	createErrors       []error
	deletedTransferIDs []uint
	deleteErrors       map[uint]error
}

func (f *fakeSystemTransferExecutor) CreateSystemTransfer(_ context.Context, req *transferSvc.SystemTransferRequest) (*entity.StockTransfer, error) {
	f.createRequests = append(f.createRequests, req)
	idx := len(f.createRequests) - 1
	if idx < len(f.createErrors) && f.createErrors[idx] != nil {
		return nil, f.createErrors[idx]
	}
	if idx < len(f.createResponses) && f.createResponses[idx] != nil {
		return f.createResponses[idx], nil
	}
	return &entity.StockTransfer{Id: uint64(1000 + idx), MovementNumber: "PND-LTI-DEFAULT"}, nil
}

func (f *fakeSystemTransferExecutor) DeleteSystemTransfer(_ context.Context, id uint, _ uint) error {
	f.deletedTransferIDs = append(f.deletedTransferIDs, id)
	if f.deleteErrors != nil {
		return f.deleteErrors[id]
	}
	return nil
}

// ── applyFarmWarehouseOverride ────────────────────────────────────────────────

func TestApplyOverrideResolvesMultiFarmLocation(t *testing.T) {
	farmMap := map[uint]farmWarehouseInfo{
		10: {
			LocationID:   10,
			LocationName: "Cijangkar",
			AllFarm: []farmWarehouseEntry{
				{ID: 50, Name: "Farm A"},
				{ID: 51, Name: "Farm B"},
			},
		},
	}

	if err := applyFarmWarehouseOverride(farmMap, 51); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	info := farmMap[10]
	if info.ChosenID != 51 {
		t.Errorf("expected ChosenID=51, got %d", info.ChosenID)
	}
	if info.ChosenName != "Farm B" {
		t.Errorf("expected ChosenName=Farm B, got %s", info.ChosenName)
	}
	if len(info.OtherFarm) != 1 || info.OtherFarm[0].ID != 50 {
		t.Errorf("expected OtherFarm=[Farm A], got %+v", info.OtherFarm)
	}
}

func TestApplyOverrideErrorsWhenIDNotInAllFarm(t *testing.T) {
	farmMap := map[uint]farmWarehouseInfo{
		10: {
			LocationID:   10,
			LocationName: "Cijangkar",
			AllFarm: []farmWarehouseEntry{
				{ID: 50, Name: "Farm A"},
				{ID: 51, Name: "Farm B"},
			},
		},
	}

	err := applyFarmWarehouseOverride(farmMap, 99)
	if err == nil {
		t.Fatal("expected error for unknown warehouse id, got nil")
	}
	if !strings.Contains(err.Error(), "99") {
		t.Errorf("error should mention the invalid id, got: %v", err)
	}
	if !strings.Contains(err.Error(), "Farm A") || !strings.Contains(err.Error(), "Farm B") {
		t.Errorf("error should list available warehouses, got: %v", err)
	}
}

func TestApplyOverrideIgnoresSingleFarmLocations(t *testing.T) {
	farmMap := map[uint]farmWarehouseInfo{
		10: {
			LocationID:   10,
			LocationName: "Jamali",
			AllFarm:      []farmWarehouseEntry{{ID: 50, Name: "Farm A"}},
			ChosenID:     50,
			ChosenName:   "Farm A",
		},
	}

	// Override ID 50 is present, but there is only 1 farm; the function should
	// not touch this location (no OtherFarm to populate).
	if err := applyFarmWarehouseOverride(farmMap, 50); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(farmMap[10].OtherFarm) != 0 {
		t.Errorf("expected no OtherFarm for single-farm location, got %+v", farmMap[10].OtherFarm)
	}
}

func TestApplyOverrideNoopWhenZero(t *testing.T) {
	farmMap := map[uint]farmWarehouseInfo{
		10: {
			LocationID:   10,
			LocationName: "Cijangkar",
			AllFarm: []farmWarehouseEntry{
				{ID: 50, Name: "Farm A"},
				{ID: 51, Name: "Farm B"},
			},
		},
	}
	if err := applyFarmWarehouseOverride(farmMap, 0); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if farmMap[10].ChosenID != 0 {
		t.Errorf("expected ChosenID unchanged (0), got %d", farmMap[10].ChosenID)
	}
}

// ── listUnresolvedLocations ───────────────────────────────────────────────────

func TestListUnresolvedLocationsReturnsOnlyAmbiguous(t *testing.T) {
	farmMap := map[uint]farmWarehouseInfo{
		1: {LocationID: 1, LocationName: "Jamali", AllFarm: []farmWarehouseEntry{{ID: 50, Name: "Farm A"}}, ChosenID: 50},
		2: {
			LocationID:   2,
			LocationName: "Cijangkar",
			AllFarm:      []farmWarehouseEntry{{ID: 60, Name: "Farm X"}, {ID: 61, Name: "Farm Y"}},
			// ChosenID = 0: unresolved
		},
		3: {LocationID: 3, LocationName: "Tamansari", AllFarm: nil}, // no farm at all, not an error here
	}

	msgs := listUnresolvedLocations(farmMap)
	if len(msgs) != 1 {
		t.Fatalf("expected 1 unresolved message, got %d: %v", len(msgs), msgs)
	}
	if !strings.Contains(msgs[0], "Cijangkar") {
		t.Errorf("message should name the ambiguous location, got: %s", msgs[0])
	}
	if !strings.Contains(msgs[0], "Farm X") || !strings.Contains(msgs[0], "Farm Y") {
		t.Errorf("message should list available warehouses, got: %s", msgs[0])
	}
}

// ── buildTransferPlan — kandang source ───────────────────────────────────────

func TestBuildPlanKandangEligibleGroupedByWarehousePair(t *testing.T) {
	opts := &commandOptions{RunID: "test-run"}
	farmMap := map[uint]farmWarehouseInfo{
		10: {LocationID: 10, LocationName: "Jamali", AllFarm: []farmWarehouseEntry{{ID: 50, Name: "Farm A"}}, ChosenID: 50, ChosenName: "Farm A"},
	}
	stocks := []kandangStockRow{
		{LocationID: 10, LocationName: "Jamali", SourceWarehouseID: 20, SourceWarehouseName: "K1", ProductID: 1, ProductName: "Pakan A", OnHandQty: 100, LeftoverQty: 100, SourceType: sourceTypeKandang},
		{LocationID: 10, LocationName: "Jamali", SourceWarehouseID: 20, SourceWarehouseName: "K1", ProductID: 2, ProductName: "OVK B", OnHandQty: 50, AllocatedQty: 10, LeftoverQty: 40, SourceType: sourceTypeKandang},
	}

	reportRows, groups := buildTransferPlan(opts, farmMap, stocks)

	if len(groups) != 1 || len(groups[0].Rows) != 2 {
		t.Fatalf("expected 1 group with 2 rows, got %d groups", len(groups))
	}
	if groups[0].SourceType != sourceTypeKandang {
		t.Errorf("expected group source type kandang_to_farm, got %s", groups[0].SourceType)
	}
	for _, row := range reportRows {
		if row.Status != "eligible" {
			t.Errorf("expected eligible, got %s for %s", row.Status, row.ProductName)
		}
	}
	if reportRows[1].Qty != 40 {
		t.Errorf("expected leftover qty 40 for OVK B, got %.3f", reportRows[1].Qty)
	}
}

func TestBuildPlanSkipsMissingFarmWarehouse(t *testing.T) {
	opts := &commandOptions{RunID: "test-run"}
	farmMap := map[uint]farmWarehouseInfo{
		10: {LocationID: 10, LocationName: "Jamali", AllFarm: nil},
	}
	stocks := []kandangStockRow{
		{LocationID: 10, SourceWarehouseID: 20, ProductID: 1, ProductName: "Pakan A", OnHandQty: 100, LeftoverQty: 100, SourceType: sourceTypeKandang},
	}

	reportRows, groups := buildTransferPlan(opts, farmMap, stocks)
	if len(groups) != 0 {
		t.Fatalf("expected no groups, got %d", len(groups))
	}
	if reportRows[0].Status != "skipped" || reportRows[0].Reason != "missing_farm_warehouse" {
		t.Errorf("unexpected: %s / %s", reportRows[0].Status, reportRows[0].Reason)
	}
}

func TestBuildPlanErrorForUnresolvedMultiFarmWarehouse(t *testing.T) {
	opts := &commandOptions{RunID: "test-run"}
	farmMap := map[uint]farmWarehouseInfo{
		10: {
			LocationID:   10,
			LocationName: "Cijangkar",
			AllFarm:      []farmWarehouseEntry{{ID: 60, Name: "Farm X"}, {ID: 61, Name: "Farm Y"}},
			// ChosenID = 0: unresolved
		},
	}
	stocks := []kandangStockRow{
		{LocationID: 10, LocationName: "Cijangkar", SourceWarehouseID: 21, ProductID: 3, ProductName: "Pakan C", OnHandQty: 200, LeftoverQty: 200, SourceType: sourceTypeKandang},
	}

	reportRows, groups := buildTransferPlan(opts, farmMap, stocks)
	if len(groups) != 0 {
		t.Fatalf("expected no groups, got %d", len(groups))
	}
	if reportRows[0].Status != "error" {
		t.Errorf("expected error status, got %s", reportRows[0].Status)
	}
	if !strings.Contains(reportRows[0].Reason, "multiple_farm_warehouses") {
		t.Errorf("reason should mention multiple_farm_warehouses, got: %s", reportRows[0].Reason)
	}
	// The error message must list the available warehouses so the operator knows
	// which --farm-warehouse-id to use.
	if !strings.Contains(reportRows[0].Reason, "Farm X") || !strings.Contains(reportRows[0].Reason, "Farm Y") {
		t.Errorf("reason should list available warehouses, got: %s", reportRows[0].Reason)
	}
}

func TestBuildPlanSkipsFullyAllocatedKandangStock(t *testing.T) {
	opts := &commandOptions{RunID: "test-run"}
	farmMap := map[uint]farmWarehouseInfo{
		10: {LocationID: 10, AllFarm: []farmWarehouseEntry{{ID: 50, Name: "Farm A"}}, ChosenID: 50, ChosenName: "Farm A"},
	}
	stocks := []kandangStockRow{
		{LocationID: 10, SourceWarehouseID: 20, ProductID: 1, ProductName: "Pakan A", OnHandQty: 100, AllocatedQty: 100, LeftoverQty: 0, SourceType: sourceTypeKandang},
		{LocationID: 10, SourceWarehouseID: 20, ProductID: 2, ProductName: "OVK B", OnHandQty: 80, AllocatedQty: 30, LeftoverQty: 50, SourceType: sourceTypeKandang},
	}

	_, groups := buildTransferPlan(opts, farmMap, stocks)
	if len(groups) != 1 || len(groups[0].Rows) != 1 {
		t.Fatalf("expected 1 group with 1 eligible row, got groups=%d", len(groups))
	}
	if groups[0].Rows[0].ProductName != "OVK B" {
		t.Errorf("expected only OVK B to be eligible, got %s", groups[0].Rows[0].ProductName)
	}
}

func TestBuildPlanSkipAmbiguousDowngradesErrorToSkipped(t *testing.T) {
	opts := &commandOptions{RunID: "test-run", SkipAmbiguous: true}
	farmMap := map[uint]farmWarehouseInfo{
		10: {
			LocationID:   10,
			LocationName: "Cijangkar",
			AllFarm:      []farmWarehouseEntry{{ID: 60, Name: "Farm X"}, {ID: 61, Name: "Farm Y"}},
			// ChosenID = 0: unresolved
		},
		11: {
			LocationID:   11,
			LocationName: "Jamali",
			AllFarm:      []farmWarehouseEntry{{ID: 50, Name: "Farm A"}},
			ChosenID:     50,
			ChosenName:   "Farm A",
		},
	}
	stocks := []kandangStockRow{
		{LocationID: 10, LocationName: "Cijangkar", SourceWarehouseID: 21, ProductID: 3, ProductName: "Pakan C", OnHandQty: 200, LeftoverQty: 200, SourceType: sourceTypeKandang},
		{LocationID: 11, LocationName: "Jamali", SourceWarehouseID: 20, ProductID: 1, ProductName: "Pakan A", OnHandQty: 100, LeftoverQty: 100, SourceType: sourceTypeKandang},
	}

	reportRows, groups := buildTransferPlan(opts, farmMap, stocks)

	// Ambiguous location must be skipped, not error.
	ambiguous := reportRows[0]
	if ambiguous.LocationName != "Cijangkar" {
		t.Fatalf("expected first row to be Cijangkar, got %s", ambiguous.LocationName)
	}
	if ambiguous.Status != "skipped" {
		t.Errorf("expected skipped with --skip-ambiguous, got %s", ambiguous.Status)
	}
	if !strings.Contains(ambiguous.Reason, "multiple_farm_warehouses") {
		t.Errorf("reason should still explain the cause, got: %s", ambiguous.Reason)
	}

	// Unambiguous location must still be eligible and grouped.
	if len(groups) != 1 || groups[0].LocationName != "Jamali" {
		t.Errorf("expected 1 group for Jamali, got %d groups", len(groups))
	}
}

// ── applyFlagFilter (unit-level, via buildTransferPlan) ───────────────────────

// applyFlagFilter is a DB-level filter so we test its effect indirectly: the
// flag filter is applied before rows reach buildTransferPlan, so we simulate
// by only passing stock rows that the query would have returned.
// The real guard is that loadKandangLeftoverStocks receives the filtered set.
// Here we verify that buildTransferPlan itself is agnostic to the filter and
// simply processes whatever rows it is given.
func TestBuildPlanOnlyTransfersRowsPassedToIt(t *testing.T) {
	opts := &commandOptions{RunID: "test-run", FlagFilter: []string{"PAKAN"}}
	farmMap := map[uint]farmWarehouseInfo{
		10: {LocationID: 10, AllFarm: []farmWarehouseEntry{{ID: 50, Name: "Farm A"}}, ChosenID: 50, ChosenName: "Farm A"},
	}
	// Simulate: only PAKAN products survived the DB filter; OVK was excluded.
	stocks := []kandangStockRow{
		{LocationID: 10, SourceWarehouseID: 20, ProductID: 1, ProductName: "Pakan Broiler", OnHandQty: 100, LeftoverQty: 100, SourceType: sourceTypeKandang},
	}

	_, groups := buildTransferPlan(opts, farmMap, stocks)
	if len(groups) != 1 || len(groups[0].Rows) != 1 {
		t.Fatalf("expected 1 group with 1 row, got %d groups", len(groups))
	}
	if groups[0].Rows[0].ProductName != "Pakan Broiler" {
		t.Errorf("unexpected product: %s", groups[0].Rows[0].ProductName)
	}
}

// ── buildTransferPlan — farm_consolidation source ────────────────────────────

func TestBuildPlanFarmConsolidationCreatesOwnGroup(t *testing.T) {
	opts := &commandOptions{RunID: "test-run"}
	// Location 10 has 2 farm warehouses; Farm B (id=51) was chosen, Farm A
	// (id=50) is OtherFarm whose stocks need consolidating.
	farmMap := map[uint]farmWarehouseInfo{
		10: {
			LocationID:   10,
			LocationName: "Cijangkar",
			AllFarm:      []farmWarehouseEntry{{ID: 50, Name: "Farm A"}, {ID: 51, Name: "Farm B"}},
			ChosenID:     51,
			ChosenName:   "Farm B",
			OtherFarm:    []farmWarehouseEntry{{ID: 50, Name: "Farm A"}},
		},
	}
	// Extra farm stock from Farm A + normal kandang stock.
	stocks := []kandangStockRow{
		{LocationID: 10, LocationName: "Cijangkar", SourceWarehouseID: 20, SourceWarehouseName: "Kandang K1", ProductID: 1, ProductName: "Pakan A", OnHandQty: 100, LeftoverQty: 100, SourceType: sourceTypeKandang},
		{LocationID: 10, LocationName: "Cijangkar", SourceWarehouseID: 50, SourceWarehouseName: "Farm A", ProductID: 2, ProductName: "OVK B", OnHandQty: 60, LeftoverQty: 60, SourceType: sourceTypeFarmConsol},
	}

	reportRows, groups := buildTransferPlan(opts, farmMap, stocks)

	if len(groups) != 2 {
		t.Fatalf("expected 2 groups (one per source warehouse), got %d", len(groups))
	}
	if len(reportRows) != 2 {
		t.Fatalf("expected 2 report rows, got %d", len(reportRows))
	}

	groupsBySource := make(map[uint]*transferGroup, 2)
	for i := range groups {
		groupsBySource[groups[i].SourceWarehouseID] = &groups[i]
	}

	kandangGroup := groupsBySource[20]
	if kandangGroup == nil {
		t.Fatal("expected a group with SourceWarehouseID=20")
	}
	if kandangGroup.SourceType != sourceTypeKandang {
		t.Errorf("expected kandang_to_farm group, got %s", kandangGroup.SourceType)
	}
	if kandangGroup.FarmWarehouseID != 51 {
		t.Errorf("expected kandang group to target Farm B (51), got %d", kandangGroup.FarmWarehouseID)
	}

	consolGroup := groupsBySource[50]
	if consolGroup == nil {
		t.Fatal("expected a consolidation group with SourceWarehouseID=50")
	}
	if consolGroup.SourceType != sourceTypeFarmConsol {
		t.Errorf("expected farm_consolidation group, got %s", consolGroup.SourceType)
	}
	if consolGroup.FarmWarehouseID != 51 {
		t.Errorf("expected consolidation group to target Farm B (51), got %d", consolGroup.FarmWarehouseID)
	}
}

func TestBuildPlanFarmConsolidationSkipsZeroLeftover(t *testing.T) {
	opts := &commandOptions{RunID: "test-run"}
	farmMap := map[uint]farmWarehouseInfo{
		10: {LocationID: 10, AllFarm: []farmWarehouseEntry{{ID: 50}, {ID: 51}}, ChosenID: 51, ChosenName: "Farm B", OtherFarm: []farmWarehouseEntry{{ID: 50, Name: "Farm A"}}},
	}
	stocks := []kandangStockRow{
		{LocationID: 10, SourceWarehouseID: 50, ProductID: 1, ProductName: "Pakan A", OnHandQty: 100, AllocatedQty: 100, LeftoverQty: 0, SourceType: sourceTypeFarmConsol},
	}

	reportRows, groups := buildTransferPlan(opts, farmMap, stocks)
	if len(groups) != 0 {
		t.Fatalf("expected no groups, got %d", len(groups))
	}
	if reportRows[0].Status != "skipped" {
		t.Errorf("expected skipped, got %s", reportRows[0].Status)
	}
}

// ── executeApply ──────────────────────────────────────────────────────────────

func TestExecuteApplyTagsReasonAndNotesWithSourceType(t *testing.T) {
	date := time.Date(2026, 4, 24, 0, 0, 0, 0, time.UTC)
	opts := &commandOptions{RunID: "run-apply", TransferDate: date, ActorID: 99}

	groups := []transferGroup{
		{
			SourceType:          sourceTypeKandang,
			LocationName:        "Jamali",
			SourceWarehouseID:   20,
			SourceWarehouseName: "K1",
			FarmWarehouseID:     50,
			FarmWarehouseName:   "Farm A",
			Rows:                []*transferReportRow{{ProductID: 1, ProductName: "Pakan A", Qty: 100}},
		},
		{
			SourceType:          sourceTypeFarmConsol,
			LocationName:        "Cijangkar",
			SourceWarehouseID:   60,
			SourceWarehouseName: "Farm X",
			FarmWarehouseID:     61,
			FarmWarehouseName:   "Farm Y",
			Rows:                []*transferReportRow{{ProductID: 2, ProductName: "OVK B", Qty: 40}},
		},
	}

	executor := &fakeSystemTransferExecutor{}
	summary, err := executeApply(context.Background(), executor, opts, groups)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if summary.GroupsApplied != 2 {
		t.Errorf("expected 2 groups applied, got %d", summary.GroupsApplied)
	}
	if len(executor.createRequests) != 2 {
		t.Fatalf("expected 2 create requests, got %d", len(executor.createRequests))
	}

	// Both requests must carry the run_id in the reason for rollback to work.
	for i, req := range executor.createRequests {
		if !strings.Contains(req.TransferReason, "run_id=run-apply") {
			t.Errorf("request %d reason missing run_id: %s", i, req.TransferReason)
		}
	}

	// Notes for farm_consolidation should be distinct from kandang_to_farm.
	if !strings.Contains(executor.createRequests[0].StockLogNotes, "kandang to farm") {
		t.Errorf("kandang group notes should say 'kandang to farm', got: %s", executor.createRequests[0].StockLogNotes)
	}
	if !strings.Contains(executor.createRequests[1].StockLogNotes, "consolidation") {
		t.Errorf("consolidation group notes should say 'consolidation', got: %s", executor.createRequests[1].StockLogNotes)
	}
}

func TestExecuteApplyCreatesTransferWithCorrectProductsAndRecordsTransferID(t *testing.T) {
	date := time.Date(2026, 4, 24, 0, 0, 0, 0, time.UTC)
	opts := &commandOptions{RunID: "run-1", TransferDate: date, ActorID: 1}

	row1 := &transferReportRow{ProductID: 1, ProductName: "Pakan A", Qty: 100}
	row2 := &transferReportRow{ProductID: 2, ProductName: "OVK B", Qty: 40}
	groups := []transferGroup{
		{
			SourceType: sourceTypeKandang, SourceWarehouseID: 20, FarmWarehouseID: 50,
			Rows: []*transferReportRow{row1, row2},
		},
	}

	executor := &fakeSystemTransferExecutor{
		createResponses: []*entity.StockTransfer{{Id: 1001, MovementNumber: "PND-LTI-1001"}},
	}

	_, err := executeApply(context.Background(), executor, opts, groups)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if row1.Status != "applied" || row2.Status != "applied" {
		t.Errorf("both rows should be applied: %s, %s", row1.Status, row2.Status)
	}
	if row1.TransferID == nil || *row1.TransferID != 1001 {
		t.Errorf("expected transfer id 1001, got %+v", row1.TransferID)
	}
	if row1.MovementNumber == nil || *row1.MovementNumber != "PND-LTI-1001" {
		t.Errorf("expected movement number PND-LTI-1001, got %+v", row1.MovementNumber)
	}

	// Verify both products were included in the create request.
	if len(executor.createRequests[0].Products) != 2 {
		t.Errorf("expected 2 products in request, got %d", len(executor.createRequests[0].Products))
	}
}

// ── executeRollback ───────────────────────────────────────────────────────────

func TestExecuteRollbackDeletesDescendingAndMarksStatuses(t *testing.T) {
	executor := &fakeSystemTransferExecutor{
		deleteErrors: map[uint]error{200: errors.New("already consumed")},
	}
	rows := []rollbackDetailRow{
		{TransferID: 100, ProductName: "Pakan A"},
		{TransferID: 200, ProductName: "OVK B"},
		{TransferID: 100, ProductName: "Pakan C"},
	}

	err := executeRollback(context.Background(), executor, rows, 99)
	if err == nil || !strings.Contains(err.Error(), "already consumed") {
		t.Fatalf("expected error for transfer 200, got: %v", err)
	}
	if executor.deletedTransferIDs[0] != 200 || executor.deletedTransferIDs[1] != 100 {
		t.Fatalf("expected delete order [200 100], got %v", executor.deletedTransferIDs)
	}
	if rows[0].Status != "rolled_back" || rows[2].Status != "rolled_back" {
		t.Errorf("transfer 100 rows must be rolled_back: %+v", rows)
	}
	if rows[1].Status != "failed" {
		t.Errorf("transfer 200 row must be failed: %+v", rows[1])
	}
}

func TestExecuteRollbackRequiresActorID(t *testing.T) {
	err := executeRollback(context.Background(), &fakeSystemTransferExecutor{}, []rollbackDetailRow{{TransferID: 1}}, 0)
	if err == nil || !strings.Contains(err.Error(), "actor-id") {
		t.Fatalf("expected actor-id error, got: %v", err)
	}
}

// ── buildTransferReason / buildRunReasonMatcher ───────────────────────────────

func TestBuildTransferReasonMatchesRunReasonMatcher(t *testing.T) {
	runID := "product-farm-transfer-20260424T120000.000000000Z"
	date := time.Date(2026, 4, 24, 0, 0, 0, 0, time.UTC)
	reason := buildTransferReason(runID, "Jamali", "Gudang K1", "Gudang Farm Jamali", date)

	needle := strings.TrimSuffix(buildRunReasonMatcher(runID), "%")
	if !strings.HasPrefix(reason, needle) {
		t.Errorf("reason %q does not match matcher prefix %q", reason, needle)
	}
}

func TestBuildTransferReasonSanitizesPipes(t *testing.T) {
	reason := buildTransferReason("run-1", "Lok|asi", "Gudang|K1", "Farm|WH", time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC))
	parts := strings.Split(reason, "|")
	// prefix + 5 key=value segments = 6 parts
	if len(parts) != 6 {
		t.Errorf("expected 6 pipe-separated segments, got %d: %v", len(parts), parts)
	}
}

func TestBuildStockLogNotesContainsSourceTypeHint(t *testing.T) {
	date := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
	kandangNote := buildStockLogNotes("r", "Loc", "Src", "Dst", date, sourceTypeKandang)
	consolNote := buildStockLogNotes("r", "Loc", "Src", "Dst", date, sourceTypeFarmConsol)

	if !strings.Contains(kandangNote, "kandang to farm") {
		t.Errorf("kandang note should mention 'kandang to farm': %s", kandangNote)
	}
	if !strings.Contains(consolNote, "consolidation") {
		t.Errorf("consolidation note should mention 'consolidation': %s", consolNote)
	}
}

// ── summarizeReport ───────────────────────────────────────────────────────────

func TestSummarizeReportCountsAllStatuses(t *testing.T) {
	rows := []transferReportRow{
		{Status: "eligible"},
		{Status: "applied"},
		{Status: "applied"},
		{Status: "skipped"},
		{Status: "error"},
		{Status: "failed"},
	}
	groups := []transferGroup{{}, {}}
	s := summarizeReport(rows, groups, 1)

	if s.RowsPlanned != 4 { // eligible + 2 applied + 1 failed
		t.Errorf("expected RowsPlanned=4, got %d", s.RowsPlanned)
	}
	if s.RowsApplied != 2 {
		t.Errorf("expected RowsApplied=2, got %d", s.RowsApplied)
	}
	if s.RowsSkipped != 1 {
		t.Errorf("expected RowsSkipped=1, got %d", s.RowsSkipped)
	}
	if s.RowsError != 1 {
		t.Errorf("expected RowsError=1, got %d", s.RowsError)
	}
	if s.RowsFailed != 1 {
		t.Errorf("expected RowsFailed=1, got %d", s.RowsFailed)
	}
	if s.GroupsPlanned != 2 || s.GroupsApplied != 1 {
		t.Errorf("unexpected group counts: %+v", s)
	}
}
