package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"os"
	"sort"
	"strconv"
	"strings"
	"time"

	commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
	"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"
	"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
	"gorm.io/gorm"
)

type adjustmentRow struct {
	ID                 uint      `gorm:"column:id"`
	ProductWarehouseID uint      `gorm:"column:product_warehouse_id"`
	ProductID          uint      `gorm:"column:product_id"`
	FunctionCode       string    `gorm:"column:function_code"`
	TotalQty           float64   `gorm:"column:total_qty"`
	UsageQty           float64   `gorm:"column:usage_qty"`
	PendingQty         float64   `gorm:"column:pending_qty"`
	StockLogIncrease   float64   `gorm:"column:stock_log_increase"`
	StockLogDecrease   float64   `gorm:"column:stock_log_decrease"`
	CreatedAt          time.Time `gorm:"column:created_at"`
}

type routeResolution struct {
	FlagGroupCode string `gorm:"column:flag_group_code"`
	Lane          string `gorm:"column:lane"`
	FunctionCode  string `gorm:"column:function_code"`
}

func main() {
	var (
		idsRaw string
		apply  bool
	)

	flag.StringVar(&idsRaw, "ids", "", "Comma-separated adjustment IDs (required), example: 1,2")
	flag.BoolVar(&apply, "apply", false, "Apply delete. If false, run as dry-run")
	flag.Parse()

	ids, err := parseIDs(idsRaw)
	if err != nil {
		log.Fatalf("invalid --ids: %v", err)
	}
	if len(ids) == 0 {
		log.Fatal("--ids is required")
	}

	ctx := context.Background()
	db := database.Connect(config.DBHost, config.DBName)

	fifoStockV2Svc := commonSvc.NewFifoStockV2Service(db, nil)

	adjustments, err := loadAdjustments(ctx, db, ids)
	if err != nil {
		log.Fatalf("failed to load adjustments: %v", err)
	}
	if len(adjustments) == 0 {
		log.Fatal("no adjustments found for provided IDs")
	}

	sort.Slice(adjustments, func(i, j int) bool {
		return adjustments[i].ID < adjustments[j].ID
	})

	fmt.Printf("Mode: %s\n", modeLabel(apply))
	fmt.Printf("Adjustments loaded: %d\n\n", len(adjustments))

	success := 0
	failed := 0
	skipped := 0

	for _, adj := range adjustments {
		if strings.TrimSpace(adj.FunctionCode) == "" {
			fmt.Printf("SKIP adj=%d reason=function_code empty\n", adj.ID)
			skipped++
			continue
		}

		route, err := resolveRouteByFunctionCode(ctx, db, adj.ProductID, strings.ToUpper(strings.TrimSpace(adj.FunctionCode)))
		if err != nil {
			fmt.Printf("FAIL adj=%d error=resolve route: %v\n", adj.ID, err)
			failed++
			continue
		}

		switch route.Lane {
		case "USABLE":
			desiredQty := adj.UsageQty + adj.PendingQty
			if desiredQty <= 0 && adj.StockLogDecrease > 0 {
				desiredQty = adj.StockLogDecrease
			}

			activeAlloc, err := countActiveUsableAllocations(ctx, db, fifo.UsableKeyAdjustmentOut.String(), adj.ID)
			if err != nil {
				fmt.Printf("FAIL adj=%d error=count usable allocations: %v\n", adj.ID, err)
				failed++
				continue
			}

			fmt.Printf(
				"PLAN adj=%d lane=USABLE function=%s usage=%.3f pending=%.3f active_alloc=%d action=reflow_to_zero+delete\n",
				adj.ID,
				route.FunctionCode,
				adj.UsageQty,
				adj.PendingQty,
				activeAlloc,
			)

			if !apply {
				skipped++
				continue
			}

			err = db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
				reflowReq := commonSvc.FifoStockV2ReflowRequest{
					FlagGroupCode:      route.FlagGroupCode,
					ProductWarehouseID: adj.ProductWarehouseID,
					AsOf:               &adj.CreatedAt,
					IdempotencyKey:     fmt.Sprintf("delete-adjustment-usable-%d-%d", adj.ID, time.Now().UnixNano()),
					Tx:                 tx,
				}
				if _, err := fifoStockV2Svc.Reflow(ctx, reflowReq); err != nil {
					return fmt.Errorf("reflow usable to zero: %w", err)
				}

				if err := hardDeleteUsableAllocations(ctx, tx, fifo.UsableKeyAdjustmentOut.String(), adj.ID); err != nil {
					return err
				}
				if err := hardDeleteAdjustmentStockLogs(ctx, tx, adj.ID); err != nil {
					return err
				}
				if err := hardDeleteAdjustment(ctx, tx, adj.ID); err != nil {
					return err
				}
				return nil
			})
			if err != nil {
				fmt.Printf("FAIL adj=%d error=%v\n", adj.ID, err)
				failed++
				continue
			}

			fmt.Printf("DONE adj=%d deleted\n", adj.ID)
			success++

		case "STOCKABLE":
			removeQty := adj.TotalQty
			if removeQty <= 0 && adj.StockLogIncrease > 0 {
				removeQty = adj.StockLogIncrease
			}

			activeAlloc, err := countActiveStockableAllocations(ctx, db, fifo.StockableKeyAdjustmentIn.String(), adj.ID)
			if err != nil {
				fmt.Printf("FAIL adj=%d error=count stockable allocations: %v\n", adj.ID, err)
				failed++
				continue
			}
			if activeAlloc > 0 {
				fmt.Printf(
					"FAIL adj=%d reason=stockable still allocated active_alloc=%d action=delete blocked\n",
					adj.ID,
					activeAlloc,
				)
				failed++
				continue
			}

			fmt.Printf(
				"PLAN adj=%d lane=STOCKABLE function=%s total=%.3f remove_qty=%.3f action=reflow_to_zero+delete\n",
				adj.ID,
				route.FunctionCode,
				adj.TotalQty,
				removeQty,
			)

			if !apply {
				skipped++
				continue
			}

			err = db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
				if err := tx.WithContext(ctx).
					Table("adjustment_stocks").
					Where("id = ?", adj.ID).
					Updates(map[string]any{
						"total_qty":  0,
						"total_used": 0,
					}).Error; err != nil {
					return fmt.Errorf("set stockable qty to zero: %w", err)
				}

				reflowReq := commonSvc.FifoStockV2ReflowRequest{
					FlagGroupCode:      route.FlagGroupCode,
					ProductWarehouseID: adj.ProductWarehouseID,
					AsOf:               &adj.CreatedAt,
					IdempotencyKey:     fmt.Sprintf("delete-adjustment-stockable-%d-%d", adj.ID, time.Now().UnixNano()),
					Tx:                 tx,
				}
				if _, err := fifoStockV2Svc.Reflow(ctx, reflowReq); err != nil {
					return fmt.Errorf("reflow stockable to zero: %w", err)
				}

				if err := hardDeleteStockableAllocations(ctx, tx, fifo.StockableKeyAdjustmentIn.String(), adj.ID); err != nil {
					return err
				}
				if err := hardDeleteAdjustmentStockLogs(ctx, tx, adj.ID); err != nil {
					return err
				}
				if err := hardDeleteAdjustment(ctx, tx, adj.ID); err != nil {
					return err
				}
				return nil
			})
			if err != nil {
				fmt.Printf("FAIL adj=%d error=%v\n", adj.ID, err)
				failed++
				continue
			}

			fmt.Printf("DONE adj=%d deleted\n", adj.ID)
			success++

		default:
			fmt.Printf("SKIP adj=%d reason=unsupported lane=%s\n", adj.ID, route.Lane)
			skipped++
		}
	}

	fmt.Println()
	fmt.Printf("Summary: success=%d failed=%d skipped=%d\n", success, failed, skipped)
	if failed > 0 {
		os.Exit(1)
	}
}

func modeLabel(apply bool) string {
	if apply {
		return "APPLY"
	}
	return "DRY-RUN"
}

func parseIDs(raw string) ([]uint, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return nil, nil
	}

	parts := strings.Split(raw, ",")
	out := make([]uint, 0, len(parts))
	seen := map[uint]struct{}{}

	for _, part := range parts {
		part = strings.TrimSpace(part)
		if part == "" {
			continue
		}
		v, err := strconv.ParseUint(part, 10, 64)
		if err != nil {
			return nil, fmt.Errorf("invalid id %q", part)
		}
		if v == 0 {
			return nil, fmt.Errorf("id must be > 0: %q", part)
		}
		id := uint(v)
		if _, ok := seen[id]; ok {
			continue
		}
		seen[id] = struct{}{}
		out = append(out, id)
	}
	return out, nil
}

func loadAdjustments(ctx context.Context, db *gorm.DB, ids []uint) ([]adjustmentRow, error) {
	var rows []adjustmentRow
	err := db.WithContext(ctx).
		Table("adjustment_stocks a").
		Select(`
			a.id,
			a.product_warehouse_id,
			pw.product_id,
			a.function_code,
			COALESCE(a.total_qty, 0) AS total_qty,
			COALESCE(a.usage_qty, 0) AS usage_qty,
			COALESCE(a.pending_qty, 0) AS pending_qty,
			COALESCE((
				SELECT sl.increase
				FROM stock_logs sl
				WHERE sl.loggable_type = 'ADJUSTMENT'
					AND sl.loggable_id = a.id
				ORDER BY sl.id DESC
				LIMIT 1
			), 0) AS stock_log_increase,
			COALESCE((
				SELECT sl.decrease
				FROM stock_logs sl
				WHERE sl.loggable_type = 'ADJUSTMENT'
					AND sl.loggable_id = a.id
				ORDER BY sl.id DESC
				LIMIT 1
			), 0) AS stock_log_decrease,
			a.created_at
		`).
		Joins("JOIN product_warehouses pw ON pw.id = a.product_warehouse_id").
		Where("a.id IN ?", ids).
		Find(&rows).Error
	if err != nil {
		return nil, err
	}
	return rows, nil
}

func resolveRouteByFunctionCode(ctx context.Context, db *gorm.DB, productID uint, functionCode string) (*routeResolution, error) {
	var rows []routeResolution
	err := db.WithContext(ctx).
		Table("fifo_stock_v2_route_rules rr").
		Select("rr.flag_group_code, rr.lane, rr.function_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.function_code = ?", functionCode).
		Where(`
			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 = ?
					AND fm.flag_group_code = rr.flag_group_code
			)
		`, entity.FlagableTypeProduct, productID).
		Order("CASE WHEN rr.source_table = 'adjustment_stocks' THEN 0 ELSE 1 END ASC").
		Order("rr.id ASC").
		Find(&rows).Error
	if err != nil {
		return nil, err
	}
	if len(rows) == 0 {
		return nil, fmt.Errorf("no route found for product_id=%d function_code=%s", productID, functionCode)
	}

	selected := rows[0]
	for _, row := range rows {
		if row.Lane != selected.Lane {
			return nil, fmt.Errorf("ambiguous lane for product_id=%d function_code=%s", productID, functionCode)
		}
	}
	selected.FunctionCode = functionCode
	return &selected, nil
}

func countActiveUsableAllocations(ctx context.Context, db *gorm.DB, usableType string, usableID uint) (int64, error) {
	var count int64
	err := db.WithContext(ctx).
		Table("stock_allocations").
		Where("usable_type = ? AND usable_id = ?", usableType, usableID).
		Where("status = ?", entity.StockAllocationStatusActive).
		Where("allocation_purpose = ?", entity.StockAllocationPurposeConsume).
		Count(&count).Error
	return count, err
}

func countActiveStockableAllocations(ctx context.Context, db *gorm.DB, stockableType string, stockableID uint) (int64, error) {
	var count int64
	err := db.WithContext(ctx).
		Table("stock_allocations").
		Where("stockable_type = ? AND stockable_id = ?", stockableType, stockableID).
		Where("status = ?", entity.StockAllocationStatusActive).
		Where("allocation_purpose = ?", entity.StockAllocationPurposeConsume).
		Count(&count).Error
	return count, err
}

func hardDeleteUsableAllocations(ctx context.Context, tx *gorm.DB, usableType string, usableID uint) error {
	return tx.WithContext(ctx).
		Exec("DELETE FROM stock_allocations WHERE usable_type = ? AND usable_id = ? AND allocation_purpose = ?", usableType, usableID, entity.StockAllocationPurposeConsume).
		Error
}

func hardDeleteStockableAllocations(ctx context.Context, tx *gorm.DB, stockableType string, stockableID uint) error {
	return tx.WithContext(ctx).
		Exec("DELETE FROM stock_allocations WHERE stockable_type = ? AND stockable_id = ? AND allocation_purpose = ?", stockableType, stockableID, entity.StockAllocationPurposeConsume).
		Error
}

func hardDeleteAdjustmentStockLogs(ctx context.Context, tx *gorm.DB, adjustmentID uint) error {
	return tx.WithContext(ctx).
		Exec("DELETE FROM stock_logs WHERE loggable_type = ? AND loggable_id = ?", "ADJUSTMENT", adjustmentID).
		Error
}

func hardDeleteAdjustment(ctx context.Context, tx *gorm.DB, adjustmentID uint) error {
	return tx.WithContext(ctx).
		Exec("DELETE FROM adjustment_stocks WHERE id = ?", adjustmentID).
		Error
}
