package main

import (
	"context"
	"testing"
	"time"

	commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
	fifoStockV2 "gitlab.com/mbugroup/lti-api.git/internal/common/service/fifo_stock_v2"
	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	transferSvc "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/services"
)

func TestValidateAdjustmentGatherAgainstAllowedIDsEligible(t *testing.T) {
	result := validateAdjustmentGatherAgainstAllowedIDs(100, []uint{11, 12}, []commonSvc.FifoStockV2GatherRow{
		{SourceTable: "adjustment_stocks", SourceID: 11, AvailableQuantity: 70},
		{SourceTable: "adjustment_stocks", SourceID: 12, AvailableQuantity: 40},
	})

	if result.Status != "eligible" {
		t.Fatalf("expected eligible, got %+v", result)
	}
	if result.VerifiedQty != 100 {
		t.Fatalf("expected verified qty 100, got %v", result.VerifiedQty)
	}
}

func TestValidateAdjustmentGatherAgainstAllowedIDsRejectsMixedSource(t *testing.T) {
	result := validateAdjustmentGatherAgainstAllowedIDs(100, []uint{11}, []commonSvc.FifoStockV2GatherRow{
		{SourceTable: "adjustment_stocks", SourceID: 11, AvailableQuantity: 60},
		{SourceTable: "recording_eggs", SourceID: 21, AvailableQuantity: 50},
	})

	if result.Status != "skipped" {
		t.Fatalf("expected skipped, got %+v", result)
	}
	if result.Reason != "mixed_fifo_source_recording_eggs" {
		t.Fatalf("unexpected reason: %+v", result)
	}
}

func TestBuildAdjustmentMigrationPlanUsesValidator(t *testing.T) {
	opts := &adjustmentCommandOptions{RunID: "egg-adjustment-cutover-test"}
	farmID := uint(25)
	farmName := "Gudang Farm Jamali"
	rows := []adjustmentLegacyEggRow{
		{
			LocationID:          16,
			LocationName:        "Jamali",
			SourceWarehouseID:   46,
			SourceWarehouseName: "Gudang Jamali 1",
			FarmWarehouseID:     &farmID,
			FarmWarehouseName:   &farmName,
			ProductWarehouseID:  101,
			ProductID:           8,
			ProductName:         "Telur Utuh",
			RemainingQty:        120,
			CurrentPWQty:        150,
			AdjustmentIDs:       []uint{1},
		},
		{
			LocationID:          16,
			LocationName:        "Jamali",
			SourceWarehouseID:   46,
			SourceWarehouseName: "Gudang Jamali 1",
			FarmWarehouseID:     &farmID,
			FarmWarehouseName:   &farmName,
			ProductWarehouseID:  102,
			ProductID:           9,
			ProductName:         "Telur Putih",
			RemainingQty:        20,
			CurrentPWQty:        40,
			AdjustmentIDs:       []uint{2},
		},
		{
			LocationID:          16,
			LocationName:        "Jamali",
			SourceWarehouseID:   46,
			SourceWarehouseName: "Gudang Jamali 1",
			ProductWarehouseID:  103,
			ProductID:           10,
			ProductName:         "Telur Pecah",
			RemainingQty:        10,
			CurrentPWQty:        10,
			AdjustmentIDs:       []uint{3},
		},
	}
	validator := &fakeAdjustmentCandidateValidator{
		byProduct: map[string]adjustmentCandidateValidation{
			"Telur Utuh":  {Status: "eligible", VerifiedQty: 120},
			"Telur Putih": {Status: "skipped", Reason: "mixed_fifo_source_recording_eggs", VerifiedQty: 10},
		},
	}

	reportRows, groups := buildAdjustmentMigrationPlan(context.Background(), opts, map[uint]adjustmentLocationTiming{
		16: {LocationID: 16, LocationName: "Jamali", Status: "CLEAN_CUTOVER"},
	}, rows, validator)

	if len(reportRows) != 3 {
		t.Fatalf("expected 3 report rows, got %d", len(reportRows))
	}
	if len(groups) != 1 || len(groups[0].Rows) != 1 {
		t.Fatalf("expected only one eligible grouped row, got %+v", groups)
	}
	if reportRows[0].Status != "eligible" || reportRows[0].VerifiedQty != 120 {
		t.Fatalf("unexpected first row: %+v", reportRows[0])
	}
	if reportRows[1].Reason != "mixed_fifo_source_recording_eggs" {
		t.Fatalf("unexpected second row reason: %+v", reportRows[1])
	}
	if reportRows[2].Reason != "missing_farm_warehouse" {
		t.Fatalf("expected missing farm warehouse skip, got %+v", reportRows[2])
	}
}

func TestExecuteAdjustmentApplyRevalidatesRowsAndAppliesSubset(t *testing.T) {
	opts := &adjustmentCommandOptions{
		RunID:       "egg-adjustment-cutover-apply",
		CutoverDate: time.Date(2026, 4, 7, 0, 0, 0, 0, time.UTC),
		ActorID:     99,
	}
	group := adjustmentTransferGroup{
		LocationID:          16,
		LocationName:        "Jamali",
		SourceWarehouseID:   46,
		SourceWarehouseName: "Gudang Jamali 1",
		FarmWarehouseID:     25,
		FarmWarehouseName:   "Gudang Farm Jamali",
		Rows: []*adjustmentMigrationReportRow{
			{LocationID: 16, LocationName: "Jamali", SourceWarehouseID: 46, SourceWarehouseName: "Gudang Jamali 1", FarmWarehouseID: uintPtr(25), FarmWarehouseName: strPtr("Gudang Farm Jamali"), ProductWarehouseID: 101, ProductID: 8, ProductName: "Telur Utuh", RemainingQty: 120, CurrentPWQty: 150, AdjustmentIDs: []uint{1}, Status: "eligible"},
			{LocationID: 16, LocationName: "Jamali", SourceWarehouseID: 46, SourceWarehouseName: "Gudang Jamali 1", FarmWarehouseID: uintPtr(25), FarmWarehouseName: strPtr("Gudang Farm Jamali"), ProductWarehouseID: 102, ProductID: 9, ProductName: "Telur Putih", RemainingQty: 20, CurrentPWQty: 40, AdjustmentIDs: []uint{2}, Status: "eligible"},
		},
	}
	validator := &fakeAdjustmentCandidateValidator{
		byProduct: map[string]adjustmentCandidateValidation{
			"Telur Utuh":  {Status: "eligible", VerifiedQty: 120},
			"Telur Putih": {Status: "skipped", Reason: "mixed_fifo_source_recording_eggs", VerifiedQty: 10},
		},
	}
	executor := &fakeAdjustmentSystemTransferExecutor{
		createResponses: []*entity.StockTransfer{
			{Id: 1001, MovementNumber: "PND-LTI-1001"},
		},
	}

	summary, err := executeAdjustmentApply(context.Background(), executor, validator, opts, []adjustmentTransferGroup{group})
	if err != nil {
		t.Fatalf("expected no fatal apply error, got %v", err)
	}
	if summary.GroupsApplied != 1 {
		t.Fatalf("expected 1 applied group, got %+v", summary)
	}
	if summary.RowsApplied != 1 || summary.RowsFailed != 1 {
		t.Fatalf("unexpected summary: %+v", summary)
	}
	if len(executor.createRequests) != 1 {
		t.Fatalf("expected 1 create request, got %d", len(executor.createRequests))
	}
	if len(executor.createRequests[0].Products) != 1 || executor.createRequests[0].Products[0].ProductID != 8 {
		t.Fatalf("expected only Telur Utuh to be transferred, got %+v", executor.createRequests[0].Products)
	}
}

type fakeAdjustmentCandidateValidator struct {
	byProduct    map[string]adjustmentCandidateValidation
	errByProduct map[string]error
}

func (f *fakeAdjustmentCandidateValidator) ValidateCandidate(ctx context.Context, row adjustmentLegacyEggRow) (adjustmentCandidateValidation, error) {
	if err, ok := f.errByProduct[row.ProductName]; ok {
		return adjustmentCandidateValidation{}, err
	}
	if result, ok := f.byProduct[row.ProductName]; ok {
		return result, nil
	}
	return adjustmentCandidateValidation{Status: "eligible", VerifiedQty: row.RemainingQty}, nil
}

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

func (f *fakeAdjustmentSystemTransferExecutor) CreateSystemTransfer(ctx 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 *fakeAdjustmentSystemTransferExecutor) DeleteSystemTransfer(ctx context.Context, id uint, actorID uint) error {
	f.deletedTransferIDs = append(f.deletedTransferIDs, id)
	if f.deleteErrors == nil {
		return nil
	}
	return f.deleteErrors[id]
}

func uintPtr(v uint) *uint    { return &v }
func strPtr(v string) *string { return &v }

var _ adjustmentCandidateValidator = (*fakeAdjustmentCandidateValidator)(nil)
var _ adjustmentSystemTransferExecutor = (*fakeAdjustmentSystemTransferExecutor)(nil)
var _ commonSvc.FifoStockV2Lane = fifoStockV2.LaneStockable
