package main

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"log"
	"math"
	"os"
	"strings"

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

const qtyEpsilon = 1e-6

const (
	levelAll                = 1
	levelByProductName      = 2
	levelByProductWarehouse = 3
)

type reflowRow struct {
	ProductWarehouseID uint    `gorm:"column:product_warehouse_id"`
	ProductID          uint    `gorm:"column:product_id"`
	ProductName        string  `gorm:"column:product_name"`
	CurrentQty         float64 `gorm:"column:current_qty"`
	SumTotalQty        float64 `gorm:"column:sum_total_qty"`
	SumAllocatedQty    float64 `gorm:"column:sum_allocated_qty"`
	ComputedQty        float64 `gorm:"column:computed_qty"`
}

func main() {
	var (
		apply              bool
		level              int
		productName        string
		productWarehouseID uint
	)

	flag.BoolVar(&apply, "apply", false, "Apply changes. If false, run as dry-run")
	flag.IntVar(&level, "level", levelAll, "CLI level: 1=all product_warehouse scope, 2=product name scope, 3=product_warehouse_id scope")
	flag.StringVar(&productName, "product-name", "", "Product name (required for level 2)")
	flag.UintVar(&productWarehouseID, "product-warehouse-id", 0, "Product warehouse id (required for level 3)")
	flag.Parse()

	productName = strings.TrimSpace(productName)
	if err := validateFlags(level, productName, productWarehouseID); err != nil {
		log.Fatalf("invalid flags: %v", err)
	}

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

	rows, err := loadReflowRows(ctx, db, level, productName, productWarehouseID)
	if err != nil {
		log.Fatalf("failed to calculate reflow qty: %v", err)
	}

	fmt.Printf("Mode: %s\n", modeLabel(apply))
	fmt.Printf("Level: %d (%s)\n", level, levelLabel(level))
	if productName != "" {
		fmt.Printf("Filter product_name: %s\n", productName)
	}
	if productWarehouseID > 0 {
		fmt.Printf("Filter product_warehouse_id: %d\n", productWarehouseID)
	}
	fmt.Printf("Targets found: %d\n\n", len(rows))

	if len(rows) == 0 {
		fmt.Println("No product warehouse found from purchase_items scope")
		return
	}

	negativePlan := 0
	for _, row := range rows {
		if row.ComputedQty < 0 {
			negativePlan++
		}

		fmt.Printf(
			"PLAN pw=%d product_id=%d product=%q current_qty=%.3f total_qty=%.3f allocated_qty=%.3f computed_qty=%.3f delta=%.3f\n",
			row.ProductWarehouseID,
			row.ProductID,
			row.ProductName,
			row.CurrentQty,
			row.SumTotalQty,
			row.SumAllocatedQty,
			row.ComputedQty,
			row.ComputedQty-row.CurrentQty,
		)
	}

	if !apply {
		fmt.Println()
		fmt.Printf("Summary: planned=%d updated=0 skipped=0 failed=0 negative_plan=%d\n", len(rows), negativePlan)
		return
	}

	updated := 0
	skipped := 0
	negativeUpdated := 0

	err = db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		for _, row := range rows {
			if nearlyEqual(row.CurrentQty, row.ComputedQty) {
				fmt.Printf(
					"SKIP pw=%d reason=no_change current_qty=%.3f computed_qty=%.3f\n",
					row.ProductWarehouseID,
					row.CurrentQty,
					row.ComputedQty,
				)
				skipped++
				continue
			}

			if err := tx.Table("product_warehouses").
				Where("id = ?", row.ProductWarehouseID).
				Update("qty", row.ComputedQty).Error; err != nil {
				return fmt.Errorf("update qty for product_warehouse_id=%d: %w", row.ProductWarehouseID, err)
			}

			if row.ComputedQty < 0 {
				negativeUpdated++
			}

			fmt.Printf(
				"DONE pw=%d product_id=%d product=%q old_qty=%.3f new_qty=%.3f\n",
				row.ProductWarehouseID,
				row.ProductID,
				row.ProductName,
				row.CurrentQty,
				row.ComputedQty,
			)
			updated++
		}

		return nil
	})
	if err != nil {
		fmt.Println()
		fmt.Printf(
			"Summary: planned=%d updated=%d skipped=%d failed=1 negative_plan=%d negative_updated=%d\n",
			len(rows),
			updated,
			skipped,
			negativePlan,
			negativeUpdated,
		)
		log.Printf("error: %v", err)
		os.Exit(1)
	}

	fmt.Println()
	fmt.Printf(
		"Summary: planned=%d updated=%d skipped=%d failed=0 negative_plan=%d negative_updated=%d\n",
		len(rows),
		updated,
		skipped,
		negativePlan,
		negativeUpdated,
	)
}

func validateFlags(level int, productName string, productWarehouseID uint) error {
	switch level {
	case levelAll:
		if productName != "" {
			return errors.New("--product-name cannot be used on level 1")
		}
		if productWarehouseID > 0 {
			return errors.New("--product-warehouse-id cannot be used on level 1")
		}
	case levelByProductName:
		if productName == "" {
			return errors.New("--product-name is required on level 2")
		}
		if productWarehouseID > 0 {
			return errors.New("--product-warehouse-id cannot be used on level 2")
		}
	case levelByProductWarehouse:
		if productWarehouseID == 0 {
			return errors.New("--product-warehouse-id is required on level 3")
		}
		if productName != "" {
			return errors.New("--product-name cannot be used on level 3")
		}
	default:
		return fmt.Errorf("unsupported --level=%d (allowed: 1, 2, 3)", level)
	}

	return nil
}

func loadReflowRows(
	ctx context.Context,
	db *gorm.DB,
	level int,
	productName string,
	productWarehouseID uint,
) ([]reflowRow, error) {
	allocSub := db.WithContext(ctx).
		Table("stock_allocations sa").
		Select(`
			sa.stockable_id,
			COALESCE(SUM(sa.qty), 0) AS used_qty
		`).
		Where("sa.stockable_type = ?", fifo.StockableKeyPurchaseItems.String()).
		Where("sa.status = ?", entity.StockAllocationStatusActive).
		Where("sa.deleted_at IS NULL").
		Group("sa.stockable_id")

	calcSub := db.WithContext(ctx).
		Table("purchase_items pi").
		Select(`
			pi.product_warehouse_id,
			COALESCE(SUM(pi.total_qty), 0) AS sum_total_qty,
			COALESCE(SUM(COALESCE(alloc.used_qty, 0)), 0) AS sum_allocated_qty,
			COALESCE(SUM(COALESCE(pi.total_qty, 0) - COALESCE(alloc.used_qty, 0)), 0) AS computed_qty
		`).
		Joins("LEFT JOIN (?) alloc ON alloc.stockable_id = pi.id", allocSub).
		Where("pi.product_warehouse_id IS NOT NULL").
		Group("pi.product_warehouse_id")

	query := db.WithContext(ctx).
		Table("product_warehouses pw").
		Select(`
			pw.id AS product_warehouse_id,
			pw.product_id AS product_id,
			p.name AS product_name,
			COALESCE(pw.qty, 0) AS current_qty,
			calc.sum_total_qty,
			calc.sum_allocated_qty,
			calc.computed_qty
		`).
		Joins("JOIN products p ON p.id = pw.product_id").
		Joins("JOIN (?) calc ON calc.product_warehouse_id = pw.id", calcSub).
		Order("pw.id ASC")

	switch level {
	case levelByProductName:
		query = query.Where("LOWER(p.name) = LOWER(?)", productName)
	case levelByProductWarehouse:
		query = query.Where("pw.id = ?", productWarehouseID)
	}

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

	return rows, nil
}

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

func levelLabel(level int) string {
	switch level {
	case levelAll:
		return "all product_warehouse from purchase_items"
	case levelByProductName:
		return "specific product name"
	case levelByProductWarehouse:
		return "specific product_warehouse_id"
	default:
		return "unknown"
	}
}

func nearlyEqual(a, b float64) bool {
	return math.Abs(a-b) <= qtyEpsilon
}
