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"
)

func TestBuildMigrationPlanSkipsOverlapAndGroupsEligibleRows(t *testing.T) {
	opts := &commandOptions{
		RunID:          "egg-cutover-test",
		IncludeOverlap: false,
	}
	timings := map[uint]locationTiming{
		16: {LocationID: 16, LocationName: "Jamali", Status: "CLEAN_CUTOVER"},
		17: {LocationID: 17, LocationName: "Cijangkar", Status: "OVERLAP"},
	}
	farmID := uint(25)
	farmName := "Gudang Farm Jamali"

	rows := []legacyEggStockRow{
		{
			LocationID:          16,
			LocationName:        "Jamali",
			SourceWarehouseID:   46,
			SourceWarehouseName: "Gudang Jamali 1",
			FarmWarehouseID:     &farmID,
			FarmWarehouseName:   &farmName,
			ProductWarehouseID:  101,
			ProductID:           8,
			ProductName:         "Telur Utuh",
			OnHandQty:           120,
		},
		{
			LocationID:          16,
			LocationName:        "Jamali",
			SourceWarehouseID:   46,
			SourceWarehouseName: "Gudang Jamali 1",
			FarmWarehouseID:     &farmID,
			FarmWarehouseName:   &farmName,
			ProductWarehouseID:  102,
			ProductID:           9,
			ProductName:         "Telur Putih",
			OnHandQty:           20,
		},
		{
			LocationID:          17,
			LocationName:        "Cijangkar",
			SourceWarehouseID:   51,
			SourceWarehouseName: "Gudang Cijangkar 1",
			FarmWarehouseID:     &farmID,
			FarmWarehouseName:   &farmName,
			ProductWarehouseID:  103,
			ProductID:           10,
			ProductName:         "Telur Jumbo",
			OnHandQty:           10,
		},
		{
			LocationID:          16,
			LocationName:        "Jamali",
			SourceWarehouseID:   46,
			SourceWarehouseName: "Gudang Jamali 1",
			ProductWarehouseID:  104,
			ProductID:           11,
			ProductName:         "Telur Papacal",
			OnHandQty:           50,
		},
		{
			LocationID:          16,
			LocationName:        "Jamali",
			SourceWarehouseID:   46,
			SourceWarehouseName: "Gudang Jamali 1",
			FarmWarehouseID:     &farmID,
			FarmWarehouseName:   &farmName,
			ProductWarehouseID:  105,
			ProductID:           12,
			ProductName:         "Telur Retak",
			OnHandQty:           0,
		},
	}

	reportRows, groups := buildMigrationPlan(opts, timings, rows)

	if len(reportRows) != 5 {
		t.Fatalf("expected 5 report rows, got %d", len(reportRows))
	}
	if len(groups) != 1 {
		t.Fatalf("expected 1 eligible transfer group, got %d", len(groups))
	}
	if len(groups[0].Rows) != 2 {
		t.Fatalf("expected 2 eligible products in the transfer group, got %d", len(groups[0].Rows))
	}

	statusByProduct := make(map[string]string, len(reportRows))
	reasonByProduct := make(map[string]string, len(reportRows))
	for _, row := range reportRows {
		statusByProduct[row.ProductName] = row.Status
		reasonByProduct[row.ProductName] = row.Reason
	}

	if statusByProduct["Telur Utuh"] != "eligible" || statusByProduct["Telur Putih"] != "eligible" {
		t.Fatalf("expected Jamali egg rows to stay eligible, got statuses %+v", statusByProduct)
	}
	if reasonByProduct["Telur Jumbo"] != "overlap_location" {
		t.Fatalf("expected overlap location skip, got %q", reasonByProduct["Telur Jumbo"])
	}
	if reasonByProduct["Telur Papacal"] != "missing_farm_warehouse" {
		t.Fatalf("expected missing farm warehouse skip, got %q", reasonByProduct["Telur Papacal"])
	}
	if reasonByProduct["Telur Retak"] != "non_positive_qty" {
		t.Fatalf("expected non positive qty skip, got %q", reasonByProduct["Telur Retak"])
	}
}

func TestExecuteApplyBuildsTaggedSystemTransfersAndSummaries(t *testing.T) {
	opts := &commandOptions{
		RunID:       "egg-cutover-apply",
		CutoverDate: time.Date(2026, 4, 7, 0, 0, 0, 0, time.UTC),
		ActorID:     99,
	}
	groups := []transferGroup{
		{
			LocationID:          16,
			LocationName:        "Jamali",
			SourceWarehouseID:   46,
			SourceWarehouseName: "Gudang Jamali 1",
			FarmWarehouseID:     25,
			FarmWarehouseName:   "Gudang Farm Jamali",
			Rows: []*migrationReportRow{
				{ProductID: 8, ProductName: "Telur Utuh", Qty: 120},
				{ProductID: 9, ProductName: "Telur Putih", Qty: 20},
			},
		},
		{
			LocationID:          18,
			LocationName:        "Tamansari",
			SourceWarehouseID:   91,
			SourceWarehouseName: "Gudang Tamansari 1",
			FarmWarehouseID:     31,
			FarmWarehouseName:   "Gudang Farm Tamansari",
			Rows: []*migrationReportRow{
				{ProductID: 10, ProductName: "Telur Jumbo", Qty: 10},
			},
		},
	}

	executor := &fakeSystemTransferExecutor{
		createResponses: []*entity.StockTransfer{
			{Id: 1001, MovementNumber: "PND-LTI-1001"},
		},
		createErrors: []error{
			nil,
			errors.New("destination warehouse locked"),
		},
	}

	summary, err := executeApply(context.Background(), executor, opts, groups)
	if err != nil {
		t.Fatalf("expected no fatal apply error, got %v", err)
	}
	if summary.GroupsPlanned != 2 || summary.GroupsApplied != 1 {
		t.Fatalf("unexpected group summary: %+v", summary)
	}
	if summary.RowsApplied != 2 || summary.RowsFailed != 1 {
		t.Fatalf("unexpected row summary: %+v", summary)
	}
	if len(executor.createRequests) != 2 {
		t.Fatalf("expected 2 create requests, got %d", len(executor.createRequests))
	}
	if !strings.Contains(executor.createRequests[0].TransferReason, "EGG_FARM_CUTOVER|run_id=egg-cutover-apply|location=Jamali|cutover_date=2026-04-07") {
		t.Fatalf("unexpected transfer reason: %s", executor.createRequests[0].TransferReason)
	}
	if executor.createRequests[0].MovementNumber != "" {
		t.Fatalf("apply path should let transfer service generate movement number, got %q", executor.createRequests[0].MovementNumber)
	}
	if groups[0].Rows[0].Status != "applied" || groups[0].Rows[1].Status != "applied" {
		t.Fatalf("expected first group rows to be applied, got %+v", groups[0].Rows)
	}
	if groups[1].Rows[0].Status != "failed" {
		t.Fatalf("expected second group row to fail, got %+v", groups[1].Rows[0])
	}
	if groups[0].Rows[0].TransferID == nil || *groups[0].Rows[0].TransferID != 1001 {
		t.Fatalf("expected first row to keep created transfer id, got %+v", groups[0].Rows[0].TransferID)
	}
}

func TestExecuteRollbackDeletesTransfersDescendingAndMarksFailures(t *testing.T) {
	executor := &fakeSystemTransferExecutor{
		deleteErrors: map[uint]error{
			101: errors.New("already consumed downstream"),
		},
	}
	rows := []rollbackDetailRow{
		{TransferID: 100, ProductName: "Telur Utuh"},
		{TransferID: 101, ProductName: "Telur Jumbo"},
		{TransferID: 100, ProductName: "Telur Putih"},
	}

	err := executeRollback(context.Background(), executor, rows, 99)
	if err == nil {
		t.Fatal("expected rollback to return the first transfer error")
	}
	if err.Error() != "already consumed downstream" {
		t.Fatalf("unexpected rollback error: %v", err)
	}
	if len(executor.deletedTransferIDs) != 2 {
		t.Fatalf("expected 2 delete calls, got %d", len(executor.deletedTransferIDs))
	}
	if executor.deletedTransferIDs[0] != 101 || executor.deletedTransferIDs[1] != 100 {
		t.Fatalf("expected delete order [101 100], got %v", executor.deletedTransferIDs)
	}
	if rows[0].Status != "rolled_back" || rows[2].Status != "rolled_back" {
		t.Fatalf("expected transfer 100 rows to be rolled back, got %+v", rows)
	}
	if rows[1].Status != "failed" {
		t.Fatalf("expected transfer 101 row to fail, got %+v", rows[1])
	}
}

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

func (f *fakeSystemTransferExecutor) 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 *fakeSystemTransferExecutor) 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]
}
