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"
	"gorm.io/gorm"
)

const (
	levelAllNoFlagProducts = 1
	levelProductName       = 2
	levelProductWarehouse  = 3
	qtyEpsilon             = 1e-6
)

type targetRow 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"`
	ComputedQty        float64 `gorm:"column:computed_qty"`
}

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

	flag.IntVar(
		&level,
		"level",
		levelAllNoFlagProducts,
		"CLI level: 1=all products without flags, 2=specific product name (with flags), 3=specific product warehouse id",
	)
	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.BoolVar(&apply, "apply", false, "Apply changes. If false, run as dry-run")
	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)

	targets, err := loadTargets(ctx, db, level, productName, productWarehouseID)
	if err != nil {
		log.Fatalf("failed to load target product warehouses: %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(targets))

	if len(targets) == 0 {
		fmt.Println("No matching product warehouse rows to process")
		return
	}

	for _, row := range targets {
		fmt.Printf(
			"PLAN pw=%d product_id=%d product=%q current_qty=%.3f computed_qty=%.3f delta=%.3f\n",
			row.ProductWarehouseID,
			row.ProductID,
			row.ProductName,
			row.CurrentQty,
			row.ComputedQty,
			row.ComputedQty-row.CurrentQty,
		)
	}

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

	updated := 0
	skipped := 0
	err = db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		for _, row := range targets {
			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)
			}

			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\n", len(targets), updated, skipped)
		log.Printf("error: %v", err)
		os.Exit(1)
	}

	fmt.Println()
	fmt.Printf("Summary: planned=%d updated=%d skipped=%d failed=0\n", len(targets), updated, skipped)
}

func validateFlags(level int, productName string, productWarehouseID uint) error {
	switch level {
	case levelAllNoFlagProducts:
		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 levelProductName:
		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 levelProductWarehouse:
		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 loadTargets(
	ctx context.Context,
	db *gorm.DB,
	level int,
	productName string,
	productWarehouseID uint,
) ([]targetRow, error) {
	switch level {
	case levelAllNoFlagProducts:
		return loadTargetsLevel1ByProductWithoutFlags(ctx, db)
	case levelProductName:
		return loadTargetsLevel2ByProductWarehouseWithFlags(ctx, db, productName)
	case levelProductWarehouse:
		return loadTargetByProductWarehouseID(ctx, db, productWarehouseID)
	default:
		return nil, fmt.Errorf("unsupported level %d", level)
	}
}

func loadTargetsLevel1ByProductWithoutFlags(ctx context.Context, db *gorm.DB) ([]targetRow, error) {
	rows := make([]targetRow, 0)
	if err := 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,
			COALESCE(SUM(pi.total_qty), 0) AS computed_qty
		`).
		Joins("JOIN products p ON p.id = pw.product_id").
		Joins("LEFT JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct).
		Joins("LEFT JOIN purchase_items pi ON pi.product_warehouse_id = pw.id").
		Where("p.deleted_at IS NULL").
		Where("f.id IS NULL").
		Group("pw.id, pw.product_id, p.name, pw.qty").
		Order("pw.id ASC").
		Scan(&rows).Error; err != nil {
		return nil, err
	}

	return rows, nil
}

func loadTargetsLevel2ByProductWarehouseWithFlags(
	ctx context.Context,
	db *gorm.DB,
	productName string,
) ([]targetRow, error) {
	rows := make([]targetRow, 0)
	if err := 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,
			COALESCE(SUM(pi.total_qty), 0) AS computed_qty
		`).
		Joins("JOIN products p ON p.id = pw.product_id").
		Joins("LEFT JOIN purchase_items pi ON pi.product_warehouse_id = pw.id").
		Where("p.deleted_at IS NULL").
		Where(`
			EXISTS (
				SELECT 1
				FROM flags f
				WHERE f.flagable_id = p.id
				  AND f.flagable_type = ?
			)
		`, entity.FlagableTypeProduct).
		Where("LOWER(p.name) = LOWER(?)", productName).
		Group("pw.id, pw.product_id, p.name, pw.qty").
		Order("pw.id ASC").
		Scan(&rows).Error; err != nil {
		return nil, err
	}

	return rows, nil
}

func loadTargetByProductWarehouseID(ctx context.Context, db *gorm.DB, productWarehouseID uint) ([]targetRow, error) {
	rows := make([]targetRow, 0)
	if err := 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,
			COALESCE(SUM(pi.total_qty), 0) AS computed_qty
		`).
		Joins("JOIN products p ON p.id = pw.product_id").
		Joins("LEFT JOIN purchase_items pi ON pi.product_warehouse_id = pw.id").
		Where("pw.id = ?", productWarehouseID).
		Group("pw.id, pw.product_id, p.name, pw.qty").
		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 levelAllNoFlagProducts:
		return "all products without flags (source: purchase_items by product_warehouse_id)"
	case levelProductName:
		return "specific product name with flags (source: purchase_items by product_warehouse_id)"
	case levelProductWarehouse:
		return "specific product_warehouse_id (source: purchase_items by product_warehouse_id)"
	default:
		return "unknown"
	}
}

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