package main

import (
	"context"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"log"
	"os"
	"sort"
	"strconv"
	"strings"
	"text/tabwriter"
	"time"

	"github.com/go-playground/validator/v10"
	"github.com/sirupsen/logrus"
	commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
	fifoStockV2 "gitlab.com/mbugroup/lti-api.git/internal/common/service/fifo_stock_v2"
	"gitlab.com/mbugroup/lti-api.git/internal/config"
	"gitlab.com/mbugroup/lti-api.git/internal/database"
	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	pwRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
	transferRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/repositories"
	transferSvc "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/services"
	warehouseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
	pfkRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
	stockLogRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
	"gitlab.com/mbugroup/lti-api.git/internal/utils"
	"gorm.io/gorm"
)

const (
	adjustmentCutoverReasonPrefix = "EGG_FARM_ADJUSTMENT_CUTOVER"
	adjustmentOutputModeTable     = "table"
	adjustmentOutputModeJSON      = "json"
)

type adjustmentCommandOptions struct {
	Apply          bool
	DryRun         bool
	RollbackRunID  string
	LocationID     uint
	LocationName   string
	CutoverDate    time.Time
	CutoverDateRaw string
	Output         string
	ActorID        uint
	RunID          string
}

type adjustmentLocationTiming struct {
	LocationID       uint
	LocationName     string
	FirstKandangDate *time.Time
	LastKandangDate  *time.Time
	FirstFarmDate    *time.Time
	LastFarmDate     *time.Time
	Status           string
}

type adjustmentLegacyEggRow struct {
	LocationID          uint
	LocationName        string
	SourceWarehouseID   uint
	SourceWarehouseName string
	FarmWarehouseID     *uint
	FarmWarehouseName   *string
	ProductWarehouseID  uint
	ProductID           uint
	ProductName         string
	RemainingQty        float64
	CurrentPWQty        float64
	AdjustmentIDs       []uint
}

type adjustmentMigrationReportRow struct {
	RunID               string  `json:"run_id"`
	LocationID          uint    `json:"location_id"`
	LocationName        string  `json:"location_name"`
	SourceWarehouseID   uint    `json:"source_warehouse_id"`
	SourceWarehouseName string  `json:"source_warehouse_name"`
	FarmWarehouseID     *uint   `json:"farm_warehouse_id,omitempty"`
	FarmWarehouseName   *string `json:"farm_warehouse_name,omitempty"`
	ProductWarehouseID  uint    `json:"product_warehouse_id"`
	ProductID           uint    `json:"product_id"`
	ProductName         string  `json:"product_name"`
	RemainingQty        float64 `json:"remaining_qty"`
	CurrentPWQty        float64 `json:"current_pw_qty"`
	VerifiedQty         float64 `json:"verified_qty"`
	LocationStatus      string  `json:"location_status"`
	AdjustmentIDs       []uint  `json:"adjustment_ids,omitempty"`
	Status              string  `json:"status"`
	Reason              string  `json:"reason,omitempty"`
	TransferID          *uint64 `json:"transfer_id,omitempty"`
	MovementNumber      *string `json:"movement_number,omitempty"`
}

type adjustmentApplySummary struct {
	RowsPlanned   int `json:"rows_planned"`
	RowsApplied   int `json:"rows_applied"`
	RowsSkipped   int `json:"rows_skipped"`
	RowsFailed    int `json:"rows_failed"`
	GroupsPlanned int `json:"groups_planned"`
	GroupsApplied int `json:"groups_applied"`
}

type adjustmentRollbackDetailRow struct {
	RunID               string  `json:"run_id"`
	TransferID          uint64  `json:"transfer_id"`
	MovementNumber      string  `json:"movement_number"`
	LocationName        string  `json:"location_name"`
	SourceWarehouseName string  `json:"source_warehouse_name"`
	FarmWarehouseName   string  `json:"farm_warehouse_name"`
	ProductName         string  `json:"product_name"`
	Qty                 float64 `json:"qty"`
	Status              string  `json:"status"`
	Reason              string  `json:"reason,omitempty"`
}

type adjustmentTransferGroup struct {
	LocationID          uint
	LocationName        string
	SourceWarehouseID   uint
	SourceWarehouseName string
	FarmWarehouseID     uint
	FarmWarehouseName   string
	Rows                []*adjustmentMigrationReportRow
}

type adjustmentCandidateValidation struct {
	Status      string
	Reason      string
	VerifiedQty float64
}

type adjustmentCandidateValidator interface {
	ValidateCandidate(ctx context.Context, row adjustmentLegacyEggRow) (adjustmentCandidateValidation, error)
}

type liveAdjustmentCandidateValidator struct {
	db      *gorm.DB
	fifoSvc commonSvc.FifoStockV2Service
}

type adjustmentSystemTransferExecutor interface {
	CreateSystemTransfer(ctx context.Context, req *transferSvc.SystemTransferRequest) (*entity.StockTransfer, error)
	DeleteSystemTransfer(ctx context.Context, id uint, actorID uint) error
}

func main() {
	opts, err := parseAdjustmentFlags()
	if err != nil {
		log.Fatalf("invalid flags: %v", err)
	}

	db := database.Connect(config.DBHost, config.DBName)
	ctx := context.Background()
	validator := &liveAdjustmentCandidateValidator{
		db:      db,
		fifoSvc: commonSvc.NewFifoStockV2Service(db, logrus.StandardLogger()),
	}

	if strings.TrimSpace(opts.RollbackRunID) != "" {
		rows, err := loadAdjustmentRollbackDetails(ctx, db, opts.RollbackRunID)
		if err != nil {
			log.Fatalf("failed to load rollback details: %v", err)
		}
		if !opts.Apply {
			for i := range rows {
				rows[i].Status = "eligible"
			}
			renderAdjustmentRollbackReport(opts.Output, rows)
			return
		}
		if err := executeAdjustmentRollback(ctx, newAdjustmentSystemTransferService(db), rows, opts.ActorID); err != nil {
			log.Fatalf("rollback failed: %v", err)
		}
		renderAdjustmentRollbackReport(opts.Output, rows)
		return
	}

	timings, err := loadAdjustmentLocationTimings(ctx, db, opts)
	if err != nil {
		log.Fatalf("failed to load location timings: %v", err)
	}
	candidates, err := loadLegacyEggAdjustmentRows(ctx, db, opts)
	if err != nil {
		log.Fatalf("failed to load legacy adjustment egg rows: %v", err)
	}

	reportRows, groups := buildAdjustmentMigrationPlan(ctx, opts, timings, candidates, validator)
	if !opts.Apply {
		renderAdjustmentMigrationReport(opts.Output, reportRows, summarizeAdjustmentApply(reportRows, groups, 0))
		return
	}

	summary, err := executeAdjustmentApply(ctx, newAdjustmentSystemTransferService(db), validator, opts, groups)
	if err != nil {
		log.Fatalf("apply failed: %v", err)
	}
	finalRows := flattenAdjustmentGroups(groups, reportRows)
	summary = summarizeAdjustmentApply(finalRows, groups, summary.GroupsApplied)
	renderAdjustmentMigrationReport(opts.Output, finalRows, summary)
}

func parseAdjustmentFlags() (*adjustmentCommandOptions, error) {
	var opts adjustmentCommandOptions
	flag.BoolVar(&opts.Apply, "apply", false, "Apply migration. If false, run as dry-run")
	flag.BoolVar(&opts.DryRun, "dry-run", true, "Run as dry-run")
	flag.StringVar(&opts.RollbackRunID, "rollback-run-id", "", "Rollback all transfers created by the provided run id")
	flag.UintVar(&opts.LocationID, "location-id", 0, "Filter by location id")
	flag.StringVar(&opts.LocationName, "location-name", "", "Filter by exact location name")
	flag.StringVar(&opts.CutoverDateRaw, "cutover-date", "", "Cutover date in YYYY-MM-DD format")
	flag.StringVar(&opts.Output, "output", adjustmentOutputModeTable, "Output format: table or json")
	flag.UintVar(&opts.ActorID, "actor-id", 1, "Actor id used for created/deleted transfers")
	flag.Parse()

	opts.LocationName = strings.TrimSpace(opts.LocationName)
	opts.RollbackRunID = strings.TrimSpace(opts.RollbackRunID)
	opts.Output = strings.ToLower(strings.TrimSpace(opts.Output))
	if opts.Output == "" {
		opts.Output = adjustmentOutputModeTable
	}
	if opts.Output != adjustmentOutputModeTable && opts.Output != adjustmentOutputModeJSON {
		return nil, fmt.Errorf("unsupported --output=%s", opts.Output)
	}
	if opts.Apply {
		opts.DryRun = false
	}
	if opts.LocationID > 0 && opts.LocationName != "" {
		return nil, errors.New("use either --location-id or --location-name, not both")
	}
	if opts.RollbackRunID != "" {
		if opts.LocationID > 0 || opts.LocationName != "" {
			return nil, errors.New("location filters are not supported with --rollback-run-id")
		}
		if opts.CutoverDateRaw != "" {
			return nil, errors.New("--cutover-date is not used with --rollback-run-id")
		}
	} else if opts.Apply {
		if opts.LocationID == 0 && opts.LocationName == "" {
			return nil, errors.New("apply mode requires --location-id or --location-name for safety")
		}
		if strings.TrimSpace(opts.CutoverDateRaw) == "" {
			return nil, errors.New("--cutover-date is required in apply mode")
		}
	}

	if strings.TrimSpace(opts.CutoverDateRaw) == "" {
		opts.CutoverDate = normalizeAdjustmentDateOnly(time.Now().In(time.FixedZone("Asia/Jakarta", 7*3600)))
	} else {
		t, err := time.Parse("2006-01-02", opts.CutoverDateRaw)
		if err != nil {
			return nil, fmt.Errorf("invalid --cutover-date: %w", err)
		}
		opts.CutoverDate = normalizeAdjustmentDateOnly(t)
	}

	opts.RunID = buildAdjustmentRunID()
	return &opts, nil
}

func newAdjustmentSystemTransferService(db *gorm.DB) adjustmentSystemTransferExecutor {
	validate := validator.New()
	stockTransferRepo := transferRepo.NewStockTransferRepository(db)
	stockTransferDetailRepo := transferRepo.NewStockTransferDetailRepository(db)
	stockTransferDeliveryRepo := transferRepo.NewStockTransferDeliveryRepository(db)
	stockTransferDeliveryItemRepo := transferRepo.NewStockTransferDeliveryItemRepository(db)
	stockLogsRepo := stockLogRepo.NewStockLogRepository(db)
	productWarehouseRepo := pwRepo.NewProductWarehouseRepository(db)
	warehouseRepository := warehouseRepo.NewWarehouseRepository(db)
	projectFlockKandangRepo := pfkRepo.NewProjectFlockKandangRepository(db)
	projectFlockPopulationRepo := pfkRepo.NewProjectFlockPopulationRepository(db)
	fifoSvc := commonSvc.NewFifoStockV2Service(db, logrus.StandardLogger())

	return transferSvc.NewTransferService(
		validate,
		stockTransferRepo,
		stockTransferDetailRepo,
		stockTransferDeliveryRepo,
		stockTransferDeliveryItemRepo,
		stockLogsRepo,
		productWarehouseRepo,
		nil,
		warehouseRepository,
		projectFlockKandangRepo,
		projectFlockPopulationRepo,
		nil,
		fifoSvc,
		nil,
	)
}

func loadAdjustmentLocationTimings(ctx context.Context, db *gorm.DB, opts *adjustmentCommandOptions) (map[uint]adjustmentLocationTiming, error) {
	type row struct {
		LocationID       uint       `gorm:"column:location_id"`
		LocationName     string     `gorm:"column:location_name"`
		FirstKandangDate *time.Time `gorm:"column:first_kandang_date"`
		LastKandangDate  *time.Time `gorm:"column:last_kandang_date"`
		FirstFarmDate    *time.Time `gorm:"column:first_farm_date"`
		LastFarmDate     *time.Time `gorm:"column:last_farm_date"`
	}

	query := db.WithContext(ctx).
		Table("recording_eggs re").
		Select(`
			pf.location_id AS location_id,
			l.name AS location_name,
			MIN(CASE WHEN w.type = 'KANDANG' THEN DATE(r.record_datetime) END) AS first_kandang_date,
			MAX(CASE WHEN w.type = 'KANDANG' THEN DATE(r.record_datetime) END) AS last_kandang_date,
			MIN(CASE WHEN w.type = 'LOKASI' THEN DATE(r.record_datetime) END) AS first_farm_date,
			MAX(CASE WHEN w.type = 'LOKASI' THEN DATE(r.record_datetime) END) AS last_farm_date
		`).
		Joins("JOIN recordings r ON r.id = re.recording_id").
		Joins("JOIN project_flock_kandangs pk ON pk.id = COALESCE(re.project_flock_kandang_id, r.project_flock_kandangs_id)").
		Joins("JOIN project_flocks pf ON pf.id = pk.project_flock_id").
		Joins("JOIN locations l ON l.id = pf.location_id").
		Joins("JOIN product_warehouses pw ON pw.id = re.product_warehouse_id").
		Joins("JOIN warehouses w ON w.id = pw.warehouse_id").
		Group("pf.location_id, l.name")
	query = applyAdjustmentTimingLocationFilter(query, opts)

	var rows []row
	if err := query.Scan(&rows).Error; err != nil {
		return nil, err
	}

	result := make(map[uint]adjustmentLocationTiming, len(rows))
	for _, row := range rows {
		status := "KANDANG_ONLY"
		if row.FirstFarmDate != nil {
			status = "OVERLAP"
			if row.LastKandangDate == nil || row.FirstFarmDate.After(normalizeAdjustmentDateOnly(*row.LastKandangDate)) {
				status = "CLEAN_CUTOVER"
			}
		}
		result[row.LocationID] = adjustmentLocationTiming{
			LocationID:       row.LocationID,
			LocationName:     row.LocationName,
			FirstKandangDate: normalizeAdjustmentDatePtr(row.FirstKandangDate),
			LastKandangDate:  normalizeAdjustmentDatePtr(row.LastKandangDate),
			FirstFarmDate:    normalizeAdjustmentDatePtr(row.FirstFarmDate),
			LastFarmDate:     normalizeAdjustmentDatePtr(row.LastFarmDate),
			Status:           status,
		}
	}

	return result, nil
}

func loadLegacyEggAdjustmentRows(ctx context.Context, db *gorm.DB, opts *adjustmentCommandOptions) ([]adjustmentLegacyEggRow, error) {
	type row struct {
		LocationID          uint    `gorm:"column:location_id"`
		LocationName        string  `gorm:"column:location_name"`
		SourceWarehouseID   uint    `gorm:"column:source_warehouse_id"`
		SourceWarehouseName string  `gorm:"column:source_warehouse_name"`
		FarmWarehouseID     *uint   `gorm:"column:farm_warehouse_id"`
		FarmWarehouseName   *string `gorm:"column:farm_warehouse_name"`
		ProductWarehouseID  uint    `gorm:"column:product_warehouse_id"`
		ProductID           uint    `gorm:"column:product_id"`
		ProductName         string  `gorm:"column:product_name"`
		RemainingQty        float64 `gorm:"column:remaining_qty"`
		CurrentPWQty        float64 `gorm:"column:current_pw_qty"`
		AdjustmentIDs       string  `gorm:"column:adjustment_ids"`
	}

	firstFarmSub := db.WithContext(ctx).
		Table("warehouses fw").
		Select("fw.location_id AS location_id, MIN(fw.id) AS farm_warehouse_id").
		Where("fw.deleted_at IS NULL").
		Where("fw.type = ?", "LOKASI").
		Group("fw.location_id")

	query := db.WithContext(ctx).
		Table("adjustment_stocks ast").
		Select(`
			kw.location_id AS location_id,
			l.name AS location_name,
			kw.id AS source_warehouse_id,
			kw.name AS source_warehouse_name,
			fw.id AS farm_warehouse_id,
			fw.name AS farm_warehouse_name,
			pw.id AS product_warehouse_id,
			pw.product_id AS product_id,
			p.name AS product_name,
			COALESCE(pw.qty, 0) AS current_pw_qty,
			SUM(GREATEST(ast.total_qty - ast.total_used, 0)) AS remaining_qty,
			STRING_AGG(ast.id::text, ',' ORDER BY ast.id) AS adjustment_ids
		`).
		Joins("JOIN product_warehouses pw ON pw.id = ast.product_warehouse_id").
		Joins("JOIN warehouses kw ON kw.id = pw.warehouse_id AND kw.deleted_at IS NULL").
		Joins("JOIN locations l ON l.id = kw.location_id").
		Joins("JOIN products p ON p.id = pw.product_id").
		Joins("LEFT JOIN product_categories pc ON pc.id = p.product_category_id").
		Joins("LEFT JOIN (?) ff ON ff.location_id = kw.location_id", firstFarmSub).
		Joins("LEFT JOIN warehouses fw ON fw.id = ff.farm_warehouse_id").
		Where("kw.type = ?", "KANDANG").
		Where("ast.total_qty > ast.total_used").
		Where(`
			EXISTS (
				SELECT 1
				FROM flags f
				WHERE f.flagable_type = ?
				  AND f.flagable_id = p.id
				  AND (UPPER(f.name) = 'TELUR' OR UPPER(f.name) LIKE 'TELUR-%')
			)
			OR (
				NOT EXISTS (
					SELECT 1
					FROM flags f_any
					WHERE f_any.flagable_type = ?
					  AND f_any.flagable_id = p.id
				)
				AND UPPER(COALESCE(pc.code, '')) = 'EGG'
			)
		`, entity.FlagableTypeProduct, entity.FlagableTypeProduct).
		Group(`
			kw.location_id,
			l.name,
			kw.id,
			kw.name,
			fw.id,
			fw.name,
			pw.id,
			pw.product_id,
			p.name,
			pw.qty
		`).
		Order("l.name ASC, kw.name ASC, p.name ASC")
	query = applyAdjustmentLegacyLocationFilter(query, opts)

	var rows []row
	if err := query.Scan(&rows).Error; err != nil {
		return nil, err
	}

	result := make([]adjustmentLegacyEggRow, 0, len(rows))
	for _, row := range rows {
		result = append(result, adjustmentLegacyEggRow{
			LocationID:          row.LocationID,
			LocationName:        row.LocationName,
			SourceWarehouseID:   row.SourceWarehouseID,
			SourceWarehouseName: row.SourceWarehouseName,
			FarmWarehouseID:     row.FarmWarehouseID,
			FarmWarehouseName:   row.FarmWarehouseName,
			ProductWarehouseID:  row.ProductWarehouseID,
			ProductID:           row.ProductID,
			ProductName:         row.ProductName,
			RemainingQty:        row.RemainingQty,
			CurrentPWQty:        row.CurrentPWQty,
			AdjustmentIDs:       parseAdjustmentIDs(row.AdjustmentIDs),
		})
	}

	return result, nil
}

func (v *liveAdjustmentCandidateValidator) ValidateCandidate(ctx context.Context, row adjustmentLegacyEggRow) (adjustmentCandidateValidation, error) {
	if row.RemainingQty <= 0 {
		return adjustmentCandidateValidation{Status: "skipped", Reason: "non_positive_remaining_qty"}, nil
	}
	if row.ProductWarehouseID == 0 || row.ProductID == 0 {
		return adjustmentCandidateValidation{Status: "failed", Reason: "invalid_candidate"}, nil
	}

	flagGroupCode, err := resolveAdjustmentEggFlagGroup(ctx, v.db, row.ProductID)
	if err != nil {
		return adjustmentCandidateValidation{}, err
	}

	gatherRows, err := v.fifoSvc.Gather(ctx, commonSvc.FifoStockV2GatherRequest{
		FlagGroupCode:      flagGroupCode,
		Lane:               fifoStockV2.LaneStockable,
		AllocationPurpose:  entity.StockAllocationPurposeConsume,
		ProductWarehouseID: row.ProductWarehouseID,
		Limit:              1000,
	})
	if err != nil {
		return adjustmentCandidateValidation{}, err
	}

	return validateAdjustmentGatherAgainstAllowedIDs(row.RemainingQty, row.AdjustmentIDs, gatherRows), nil
}

func validateAdjustmentGatherAgainstAllowedIDs(
	needQty float64,
	allowedIDs []uint,
	rows []commonSvc.FifoStockV2GatherRow,
) adjustmentCandidateValidation {
	if needQty <= 0 {
		return adjustmentCandidateValidation{Status: "skipped", Reason: "non_positive_remaining_qty"}
	}

	allowed := make(map[uint]struct{}, len(allowedIDs))
	for _, id := range allowedIDs {
		if id > 0 {
			allowed[id] = struct{}{}
		}
	}
	if len(allowed) == 0 {
		return adjustmentCandidateValidation{Status: "failed", Reason: "missing_adjustment_ids"}
	}

	const epsilon = 1e-6
	verifiedQty := 0.0
	for _, row := range rows {
		if row.AvailableQuantity <= epsilon {
			continue
		}

		takeQty := row.AvailableQuantity
		if remaining := needQty - verifiedQty; takeQty > remaining {
			takeQty = remaining
		}
		if takeQty <= epsilon {
			continue
		}

		if row.SourceTable != "adjustment_stocks" {
			return adjustmentCandidateValidation{
				Status:      "skipped",
				Reason:      fmt.Sprintf("mixed_fifo_source_%s", strings.ToLower(strings.TrimSpace(row.SourceTable))),
				VerifiedQty: verifiedQty,
			}
		}
		if _, ok := allowed[row.SourceID]; !ok {
			return adjustmentCandidateValidation{
				Status:      "skipped",
				Reason:      "mixed_adjustment_source",
				VerifiedQty: verifiedQty,
			}
		}

		verifiedQty += takeQty
		if verifiedQty >= needQty-epsilon {
			return adjustmentCandidateValidation{
				Status:      "eligible",
				VerifiedQty: verifiedQty,
			}
		}
	}

	return adjustmentCandidateValidation{
		Status:      "skipped",
		Reason:      "insufficient_fifo_available",
		VerifiedQty: verifiedQty,
	}
}

func resolveAdjustmentEggFlagGroup(ctx context.Context, db *gorm.DB, productID uint) (string, error) {
	type row struct {
		FlagGroupCode string `gorm:"column:flag_group_code"`
	}
	var selected row
	err := db.WithContext(ctx).
		Table("fifo_stock_v2_route_rules rr").
		Select("rr.flag_group_code").
		Joins("JOIN fifo_stock_v2_flag_groups fg ON fg.code = rr.flag_group_code AND fg.is_active = TRUE").
		Where("rr.is_active = TRUE").
		Where("rr.lane = ?", "USABLE").
		Where("rr.function_code = ?", "STOCK_TRANSFER_OUT").
		Where("rr.source_table = ?", "stock_transfer_details").
		Where(`
			EXISTS (
				SELECT 1
				FROM products p
				LEFT JOIN product_categories pc ON pc.id = p.product_category_id
				WHERE p.id = ?
				  AND (
					EXISTS (
						SELECT 1
						FROM flags f
						JOIN fifo_stock_v2_flag_members fm ON fm.flag_name = f.name AND fm.is_active = TRUE
						WHERE f.flagable_type = ?
						  AND f.flagable_id = p.id
						  AND fm.flag_group_code = rr.flag_group_code
					)
					OR (
						NOT EXISTS (
							SELECT 1
							FROM flags f_any
							WHERE f_any.flagable_type = ?
							  AND f_any.flagable_id = p.id
						)
						AND rr.flag_group_code = ?
						AND UPPER(COALESCE(pc.code, '')) = 'EGG'
					)
				  )
			)
		`, productID, entity.FlagableTypeProduct, entity.FlagableTypeProduct, utils.LegacyFlagGroupCodeByProductCategoryCode("EGG")).
		Order("rr.id ASC").
		Limit(1).
		Take(&selected).Error
	if err != nil {
		return "", err
	}

	return strings.TrimSpace(selected.FlagGroupCode), nil
}

func buildAdjustmentMigrationPlan(
	ctx context.Context,
	opts *adjustmentCommandOptions,
	timings map[uint]adjustmentLocationTiming,
	rows []adjustmentLegacyEggRow,
	validator adjustmentCandidateValidator,
) ([]adjustmentMigrationReportRow, []adjustmentTransferGroup) {
	reportRows := make([]adjustmentMigrationReportRow, 0, len(rows))
	groupMap := make(map[string]*adjustmentTransferGroup)

	for _, row := range rows {
		locationStatus := "UNKNOWN"
		if timing, ok := timings[row.LocationID]; ok {
			locationStatus = timing.Status
		}

		report := adjustmentMigrationReportRow{
			RunID:               opts.RunID,
			LocationID:          row.LocationID,
			LocationName:        row.LocationName,
			SourceWarehouseID:   row.SourceWarehouseID,
			SourceWarehouseName: row.SourceWarehouseName,
			FarmWarehouseID:     row.FarmWarehouseID,
			FarmWarehouseName:   row.FarmWarehouseName,
			ProductWarehouseID:  row.ProductWarehouseID,
			ProductID:           row.ProductID,
			ProductName:         row.ProductName,
			RemainingQty:        row.RemainingQty,
			CurrentPWQty:        row.CurrentPWQty,
			LocationStatus:      locationStatus,
			AdjustmentIDs:       row.AdjustmentIDs,
			Status:              "eligible",
		}

		switch {
		case row.FarmWarehouseID == nil || row.FarmWarehouseName == nil:
			report.Status = "skipped"
			report.Reason = "missing_farm_warehouse"
		case row.RemainingQty <= 0:
			report.Status = "skipped"
			report.Reason = "non_positive_remaining_qty"
		default:
			validation, err := validator.ValidateCandidate(ctx, row)
			if err != nil {
				report.Status = "failed"
				report.Reason = err.Error()
			} else {
				report.Status = validation.Status
				report.Reason = validation.Reason
				report.VerifiedQty = validation.VerifiedQty
			}
		}

		reportRows = append(reportRows, report)
		if report.Status != "eligible" {
			continue
		}

		groupKey := fmt.Sprintf("%d:%d", row.SourceWarehouseID, *row.FarmWarehouseID)
		group := groupMap[groupKey]
		if group == nil {
			group = &adjustmentTransferGroup{
				LocationID:          row.LocationID,
				LocationName:        row.LocationName,
				SourceWarehouseID:   row.SourceWarehouseID,
				SourceWarehouseName: row.SourceWarehouseName,
				FarmWarehouseID:     *row.FarmWarehouseID,
				FarmWarehouseName:   derefAdjustmentString(row.FarmWarehouseName),
			}
			groupMap[groupKey] = group
		}
		group.Rows = append(group.Rows, &reportRows[len(reportRows)-1])
	}

	groups := make([]adjustmentTransferGroup, 0, len(groupMap))
	for _, group := range groupMap {
		sort.Slice(group.Rows, func(i, j int) bool {
			return group.Rows[i].ProductName < group.Rows[j].ProductName
		})
		groups = append(groups, *group)
	}
	sort.Slice(groups, func(i, j int) bool {
		if groups[i].LocationName == groups[j].LocationName {
			return groups[i].SourceWarehouseName < groups[j].SourceWarehouseName
		}
		return groups[i].LocationName < groups[j].LocationName
	})

	return reportRows, groups
}

func executeAdjustmentApply(
	ctx context.Context,
	svc adjustmentSystemTransferExecutor,
	validator adjustmentCandidateValidator,
	opts *adjustmentCommandOptions,
	groups []adjustmentTransferGroup,
) (adjustmentApplySummary, error) {
	summary := adjustmentApplySummary{GroupsPlanned: len(groups)}

	for _, group := range groups {
		eligibleRows := make([]*adjustmentMigrationReportRow, 0, len(group.Rows))
		for _, row := range group.Rows {
			validation, err := validator.ValidateCandidate(ctx, adjustmentLegacyEggRow{
				LocationID:          row.LocationID,
				LocationName:        row.LocationName,
				SourceWarehouseID:   row.SourceWarehouseID,
				SourceWarehouseName: row.SourceWarehouseName,
				FarmWarehouseID:     row.FarmWarehouseID,
				FarmWarehouseName:   row.FarmWarehouseName,
				ProductWarehouseID:  row.ProductWarehouseID,
				ProductID:           row.ProductID,
				ProductName:         row.ProductName,
				RemainingQty:        row.RemainingQty,
				CurrentPWQty:        row.CurrentPWQty,
				AdjustmentIDs:       row.AdjustmentIDs,
			})
			if err != nil {
				row.Status = "failed"
				row.Reason = err.Error()
				summary.RowsFailed++
				continue
			}
			if validation.Status != "eligible" {
				row.Status = "failed"
				row.Reason = validation.Reason
				row.VerifiedQty = validation.VerifiedQty
				summary.RowsFailed++
				continue
			}
			row.VerifiedQty = validation.VerifiedQty
			eligibleRows = append(eligibleRows, row)
		}

		if len(eligibleRows) == 0 {
			continue
		}

		products := make([]transferSvc.SystemTransferProduct, 0, len(eligibleRows))
		for _, row := range eligibleRows {
			products = append(products, transferSvc.SystemTransferProduct{
				ProductID:  row.ProductID,
				ProductQty: row.RemainingQty,
			})
		}

		reason := buildAdjustmentCutoverReason(opts.RunID, group.LocationName, opts.CutoverDate)
		transfer, err := svc.CreateSystemTransfer(ctx, &transferSvc.SystemTransferRequest{
			TransferReason:         reason,
			TransferDate:           opts.CutoverDate,
			SourceWarehouseID:      group.SourceWarehouseID,
			DestinationWarehouseID: group.FarmWarehouseID,
			Products:               products,
			ActorID:                opts.ActorID,
			StockLogNotes:          reason,
		})
		if err != nil {
			for _, row := range eligibleRows {
				row.Status = "failed"
				row.Reason = err.Error()
				summary.RowsFailed++
			}
			continue
		}

		summary.GroupsApplied++
		for _, row := range eligibleRows {
			row.Status = "applied"
			row.TransferID = &transfer.Id
			row.MovementNumber = &transfer.MovementNumber
			summary.RowsApplied++
		}
	}

	return summary, nil
}

func executeAdjustmentRollback(
	ctx context.Context,
	svc adjustmentSystemTransferExecutor,
	rows []adjustmentRollbackDetailRow,
	actorID uint,
) error {
	if actorID == 0 {
		return fmt.Errorf("actor id is required for rollback")
	}

	byTransfer := make(map[uint64][]int)
	for idx, row := range rows {
		byTransfer[row.TransferID] = append(byTransfer[row.TransferID], idx)
	}

	transferIDs := make([]uint64, 0, len(byTransfer))
	for transferID := range byTransfer {
		transferIDs = append(transferIDs, transferID)
	}
	sort.Slice(transferIDs, func(i, j int) bool { return transferIDs[i] > transferIDs[j] })

	var firstErr error
	for _, transferID := range transferIDs {
		err := svc.DeleteSystemTransfer(ctx, uint(transferID), actorID)
		for _, idx := range byTransfer[transferID] {
			if err != nil {
				rows[idx].Status = "failed"
				rows[idx].Reason = err.Error()
			} else {
				rows[idx].Status = "rolled_back"
			}
		}
		if err != nil && firstErr == nil {
			firstErr = err
		}
	}

	return firstErr
}

func loadAdjustmentRollbackDetails(ctx context.Context, db *gorm.DB, runID string) ([]adjustmentRollbackDetailRow, error) {
	type row struct {
		TransferID          uint64  `gorm:"column:transfer_id"`
		MovementNumber      string  `gorm:"column:movement_number"`
		LocationName        string  `gorm:"column:location_name"`
		SourceWarehouseName string  `gorm:"column:source_warehouse_name"`
		FarmWarehouseName   string  `gorm:"column:farm_warehouse_name"`
		ProductName         string  `gorm:"column:product_name"`
		Qty                 float64 `gorm:"column:qty"`
	}

	needle := buildAdjustmentRunReasonMatcher(runID)
	var dbRows []row
	err := db.WithContext(ctx).
		Table("stock_transfers st").
		Select(`
			st.id AS transfer_id,
			st.movement_number AS movement_number,
			COALESCE(loc.name, '') AS location_name,
			ws.name AS source_warehouse_name,
			wd.name AS farm_warehouse_name,
			p.name AS product_name,
			COALESCE(std.total_qty, std.usage_qty, 0) AS qty
		`).
		Joins("JOIN warehouses ws ON ws.id = st.from_warehouse_id").
		Joins("JOIN warehouses wd ON wd.id = st.to_warehouse_id").
		Joins("LEFT JOIN locations loc ON loc.id = COALESCE(ws.location_id, wd.location_id)").
		Joins("JOIN stock_transfer_details std ON std.stock_transfer_id = st.id AND std.deleted_at IS NULL").
		Joins("JOIN products p ON p.id = std.product_id").
		Where("st.deleted_at IS NULL").
		Where("st.reason LIKE ?", needle).
		Order("st.id DESC, std.id ASC").
		Scan(&dbRows).Error
	if err != nil {
		return nil, err
	}

	rows := make([]adjustmentRollbackDetailRow, 0, len(dbRows))
	for _, row := range dbRows {
		rows = append(rows, adjustmentRollbackDetailRow{
			RunID:               runID,
			TransferID:          row.TransferID,
			MovementNumber:      row.MovementNumber,
			LocationName:        row.LocationName,
			SourceWarehouseName: row.SourceWarehouseName,
			FarmWarehouseName:   row.FarmWarehouseName,
			ProductName:         row.ProductName,
			Qty:                 row.Qty,
		})
	}

	return rows, nil
}

func applyAdjustmentTimingLocationFilter(db *gorm.DB, opts *adjustmentCommandOptions) *gorm.DB {
	if opts == nil {
		return db
	}
	switch {
	case opts.LocationID > 0:
		return db.Where("pf.location_id = ?", opts.LocationID)
	case opts.LocationName != "":
		return db.Where("LOWER(l.name) = LOWER(?)", opts.LocationName)
	default:
		return db
	}
}

func applyAdjustmentLegacyLocationFilter(db *gorm.DB, opts *adjustmentCommandOptions) *gorm.DB {
	if opts == nil {
		return db
	}
	switch {
	case opts.LocationID > 0:
		return db.Where("kw.location_id = ?", opts.LocationID)
	case opts.LocationName != "":
		return db.Where("LOWER(l.name) = LOWER(?)", opts.LocationName)
	default:
		return db
	}
}

func buildAdjustmentCutoverReason(runID, locationName string, cutoverDate time.Time) string {
	locationName = strings.ReplaceAll(strings.TrimSpace(locationName), "|", "/")
	return fmt.Sprintf("%s|run_id=%s|location=%s|cutover_date=%s", adjustmentCutoverReasonPrefix, runID, locationName, cutoverDate.Format("2006-01-02"))
}

func buildAdjustmentRunReasonMatcher(runID string) string {
	return fmt.Sprintf("%s|run_id=%s|%%", adjustmentCutoverReasonPrefix, strings.TrimSpace(runID))
}

func buildAdjustmentRunID() string {
	return fmt.Sprintf("egg-adjustment-cutover-%s", time.Now().UTC().Format("20060102T150405.000000000Z"))
}

func normalizeAdjustmentDateOnly(value time.Time) time.Time {
	return time.Date(value.Year(), value.Month(), value.Day(), 0, 0, 0, 0, time.UTC)
}

func normalizeAdjustmentDatePtr(value *time.Time) *time.Time {
	if value == nil {
		return nil
	}
	normalized := normalizeAdjustmentDateOnly(*value)
	return &normalized
}

func parseAdjustmentIDs(raw string) []uint {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return nil
	}
	parts := strings.Split(raw, ",")
	out := make([]uint, 0, len(parts))
	for _, part := range parts {
		part = strings.TrimSpace(part)
		if part == "" {
			continue
		}
		value, err := strconv.ParseUint(part, 10, 64)
		if err != nil || value == 0 {
			continue
		}
		out = append(out, uint(value))
	}
	return out
}

func derefAdjustmentString(value *string) string {
	if value == nil {
		return ""
	}
	return *value
}

func summarizeAdjustmentApply(rows []adjustmentMigrationReportRow, groups []adjustmentTransferGroup, appliedGroups int) adjustmentApplySummary {
	summary := adjustmentApplySummary{
		GroupsPlanned: len(groups),
		GroupsApplied: appliedGroups,
	}
	for _, row := range rows {
		switch row.Status {
		case "eligible":
			summary.RowsPlanned++
		case "applied":
			summary.RowsPlanned++
			summary.RowsApplied++
		case "failed":
			summary.RowsPlanned++
			summary.RowsFailed++
		case "skipped":
			summary.RowsSkipped++
		}
	}
	return summary
}

func flattenAdjustmentGroups(groups []adjustmentTransferGroup, fallback []adjustmentMigrationReportRow) []adjustmentMigrationReportRow {
	if len(groups) == 0 {
		return fallback
	}
	rows := make([]adjustmentMigrationReportRow, 0, len(fallback))
	for _, group := range groups {
		for _, row := range group.Rows {
			rows = append(rows, *row)
		}
	}
	for _, row := range fallback {
		if row.Status == "skipped" {
			rows = append(rows, row)
		}
	}
	sort.Slice(rows, func(i, j int) bool {
		if rows[i].LocationName == rows[j].LocationName {
			if rows[i].SourceWarehouseName == rows[j].SourceWarehouseName {
				return rows[i].ProductName < rows[j].ProductName
			}
			return rows[i].SourceWarehouseName < rows[j].SourceWarehouseName
		}
		return rows[i].LocationName < rows[j].LocationName
	})
	return rows
}

func renderAdjustmentMigrationReport(mode string, rows []adjustmentMigrationReportRow, summary adjustmentApplySummary) {
	if mode == adjustmentOutputModeJSON {
		payload := map[string]any{
			"rows":    rows,
			"summary": summary,
		}
		enc := json.NewEncoder(os.Stdout)
		enc.SetIndent("", "  ")
		_ = enc.Encode(payload)
		return
	}

	w := tabwriter.NewWriter(os.Stdout, 2, 8, 2, ' ', 0)
	fmt.Fprintln(w, "RUN_ID\tLOCATION\tSOURCE_WAREHOUSE\tFARM_WAREHOUSE\tPRODUCT\tREMAINING_QTY\tCURRENT_PW_QTY\tVERIFIED_QTY\tLOCATION_STATUS\tSTATUS\tREASON\tTRANSFER_ID\tMOVEMENT_NUMBER")
	for _, row := range rows {
		transferID := "-"
		if row.TransferID != nil {
			transferID = fmt.Sprintf("%d", *row.TransferID)
		}
		movementNumber := "-"
		if row.MovementNumber != nil {
			movementNumber = *row.MovementNumber
		}
		fmt.Fprintf(
			w,
			"%s\t%s\t%s\t%s\t%s\t%.3f\t%.3f\t%.3f\t%s\t%s\t%s\t%s\t%s\n",
			row.RunID,
			row.LocationName,
			row.SourceWarehouseName,
			derefAdjustmentString(row.FarmWarehouseName),
			row.ProductName,
			row.RemainingQty,
			row.CurrentPWQty,
			row.VerifiedQty,
			row.LocationStatus,
			row.Status,
			row.Reason,
			transferID,
			movementNumber,
		)
	}
	_ = w.Flush()

	fmt.Printf(
		"\nSUMMARY: rows_planned=%d rows_applied=%d rows_skipped=%d rows_failed=%d groups_planned=%d groups_applied=%d\n",
		summary.RowsPlanned,
		summary.RowsApplied,
		summary.RowsSkipped,
		summary.RowsFailed,
		summary.GroupsPlanned,
		summary.GroupsApplied,
	)
}

func renderAdjustmentRollbackReport(mode string, rows []adjustmentRollbackDetailRow) {
	if mode == adjustmentOutputModeJSON {
		enc := json.NewEncoder(os.Stdout)
		enc.SetIndent("", "  ")
		_ = enc.Encode(map[string]any{"rows": rows})
		return
	}

	w := tabwriter.NewWriter(os.Stdout, 2, 8, 2, ' ', 0)
	fmt.Fprintln(w, "RUN_ID\tTRANSFER_ID\tMOVEMENT_NUMBER\tLOCATION\tSOURCE_WAREHOUSE\tFARM_WAREHOUSE\tPRODUCT\tQTY\tSTATUS\tREASON")
	for _, row := range rows {
		fmt.Fprintf(
			w,
			"%s\t%d\t%s\t%s\t%s\t%s\t%s\t%.3f\t%s\t%s\n",
			row.RunID,
			row.TransferID,
			row.MovementNumber,
			row.LocationName,
			row.SourceWarehouseName,
			row.FarmWarehouseName,
			row.ProductName,
			row.Qty,
			row.Status,
			row.Reason,
		)
	}
	_ = w.Flush()
}
