package models

import (
	"fmt"
	"math"

	"odoo-go/pkg/orm"
)

// initStockLandedCost registers stock.landed.cost, stock.landed.cost.lines,
// and stock.valuation.adjustment.lines — landed cost allocation on transfers.
// Mirrors: odoo/addons/stock_landed_costs/models/stock_landed_cost.py
func initStockLandedCost() {
	m := orm.NewModel("stock.landed.cost", orm.ModelOpts{
		Description: "Landed Costs",
		Order:       "date desc, id desc",
	})
	m.AddFields(
		orm.Char("name", orm.FieldOpts{String: "Name", Default: "New"}),
		orm.Date("date", orm.FieldOpts{String: "Date", Required: true}),
		orm.Many2many("picking_ids", "stock.picking", orm.FieldOpts{String: "Transfers"}),
		orm.One2many("cost_lines", "stock.landed.cost.lines", "cost_id", orm.FieldOpts{String: "Cost Lines"}),
		orm.One2many("valuation_adjustment_lines", "stock.valuation.adjustment.lines", "cost_id", orm.FieldOpts{String: "Valuation Adjustments"}),
		orm.Many2one("account_journal_id", "account.journal", orm.FieldOpts{String: "Journal"}),
		orm.Many2one("account_move_id", "account.move", orm.FieldOpts{String: "Journal Entry"}),
		orm.Many2one("company_id", "res.company", orm.FieldOpts{String: "Company"}),
		orm.Selection("state", []orm.SelectionItem{
			{Value: "draft", Label: "Draft"},
			{Value: "done", Label: "Posted"},
			{Value: "cancel", Label: "Cancelled"},
		}, orm.FieldOpts{String: "Status", Default: "draft"}),
		orm.Monetary("amount_total", orm.FieldOpts{String: "Total", Compute: "_compute_amount_total", CurrencyField: "currency_id"}),
		orm.Many2one("currency_id", "res.currency", orm.FieldOpts{String: "Currency"}),
	)

	// _compute_amount_total: Sum of all cost lines.
	// Mirrors: stock.landed.cost._compute_amount_total()
	m.RegisterCompute("amount_total", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		costID := rs.IDs()[0]
		var total float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(SUM(price_unit), 0) FROM stock_landed_cost_lines WHERE cost_id = $1`,
			costID,
		).Scan(&total)
		return orm.Values{"amount_total": total}, nil
	})

	// compute_landed_cost: Compute and create valuation adjustment lines based on split method.
	// Mirrors: stock.landed.cost.compute_landed_cost()
	m.RegisterMethod("compute_landed_cost", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		for _, costID := range rs.IDs() {
			// Delete existing adjustment lines
			_, err := env.Tx().Exec(env.Ctx(),
				`DELETE FROM stock_valuation_adjustment_lines WHERE cost_id = $1`, costID)
			if err != nil {
				return nil, fmt.Errorf("stock.landed.cost: clear adjustments for %d: %w", costID, err)
			}

			// Get all moves from associated pickings
			moveRows, err := env.Tx().Query(env.Ctx(),
				`SELECT sm.id, sm.product_id, sm.product_uom_qty, sm.price_unit
				 FROM stock_move sm
				 JOIN stock_picking sp ON sp.id = sm.picking_id
				 JOIN stock_landed_cost_stock_picking_rel rel ON rel.stock_picking_id = sp.id
				 WHERE rel.stock_landed_cost_id = $1 AND sm.state = 'done'`,
				costID,
			)
			if err != nil {
				return nil, fmt.Errorf("stock.landed.cost: query moves for %d: %w", costID, err)
			}

			type moveInfo struct {
				ID        int64
				ProductID int64
				Qty       float64
				UnitCost  float64
			}
			var moves []moveInfo
			var totalQty, totalWeight, totalVolume, totalCost float64

			for moveRows.Next() {
				var mi moveInfo
				if err := moveRows.Scan(&mi.ID, &mi.ProductID, &mi.Qty, &mi.UnitCost); err != nil {
					moveRows.Close()
					return nil, fmt.Errorf("stock.landed.cost: scan move: %w", err)
				}
				moves = append(moves, mi)
				totalQty += mi.Qty
				totalCost += mi.Qty * mi.UnitCost
			}
			moveRows.Close()

			if len(moves) == 0 {
				continue
			}

			// Get cost lines
			costLineRows, err := env.Tx().Query(env.Ctx(),
				`SELECT id, price_unit, split_method FROM stock_landed_cost_lines WHERE cost_id = $1`,
				costID,
			)
			if err != nil {
				return nil, fmt.Errorf("stock.landed.cost: query cost lines for %d: %w", costID, err)
			}

			type costLineInfo struct {
				ID          int64
				PriceUnit   float64
				SplitMethod string
			}
			var costLines []costLineInfo
			for costLineRows.Next() {
				var cl costLineInfo
				if err := costLineRows.Scan(&cl.ID, &cl.PriceUnit, &cl.SplitMethod); err != nil {
					costLineRows.Close()
					return nil, fmt.Errorf("stock.landed.cost: scan cost line: %w", err)
				}
				costLines = append(costLines, cl)
			}
			costLineRows.Close()

			// For each cost line, distribute costs across moves
			for _, cl := range costLines {
				for _, mv := range moves {
					var share float64
					switch cl.SplitMethod {
					case "equal":
						share = cl.PriceUnit / float64(len(moves))
					case "by_quantity":
						if totalQty > 0 {
							share = cl.PriceUnit * mv.Qty / totalQty
						}
					case "by_current_cost_price":
						moveCost := mv.Qty * mv.UnitCost
						if totalCost > 0 {
							share = cl.PriceUnit * moveCost / totalCost
						}
					case "by_weight":
						// Simplified: use quantity as proxy for weight
						if totalWeight > 0 {
							share = cl.PriceUnit * mv.Qty / totalWeight
						} else if totalQty > 0 {
							share = cl.PriceUnit * mv.Qty / totalQty
						}
					case "by_volume":
						// Simplified: use quantity as proxy for volume
						if totalVolume > 0 {
							share = cl.PriceUnit * mv.Qty / totalVolume
						} else if totalQty > 0 {
							share = cl.PriceUnit * mv.Qty / totalQty
						}
					default:
						share = cl.PriceUnit / float64(len(moves))
					}

					formerCost := mv.Qty * mv.UnitCost
					finalCost := formerCost + share

					_, err := env.Tx().Exec(env.Ctx(),
						`INSERT INTO stock_valuation_adjustment_lines
						 (cost_id, cost_line_id, move_id, product_id, quantity, former_cost, additional_landed_cost, final_cost)
						 VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
						costID, cl.ID, mv.ID, mv.ProductID, mv.Qty, formerCost, share, finalCost,
					)
					if err != nil {
						return nil, fmt.Errorf("stock.landed.cost: create adjustment line: %w", err)
					}
				}
			}
		}
		return true, nil
	})

	// button_validate: Post the landed cost, apply valuation adjustments, and set state to done.
	// Mirrors: stock.landed.cost.button_validate()
	m.RegisterMethod("button_validate", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		for _, costID := range rs.IDs() {
			// First compute the cost distribution
			lcModel := orm.Registry.Get("stock.landed.cost")
			if lcModel != nil {
				if computeMethod, ok := lcModel.Methods["compute_landed_cost"]; ok {
					lcRS := env.Model("stock.landed.cost").Browse(costID)
					if _, err := computeMethod(lcRS); err != nil {
						return nil, fmt.Errorf("stock.landed.cost: compute for validate %d: %w", costID, err)
					}
				}
			}

			// Apply adjustments to valuation layers
			adjRows, err := env.Tx().Query(env.Ctx(),
				`SELECT move_id, product_id, additional_landed_cost
				 FROM stock_valuation_adjustment_lines
				 WHERE cost_id = $1 AND additional_landed_cost != 0`,
				costID,
			)
			if err != nil {
				return nil, fmt.Errorf("stock.landed.cost: query adjustments for %d: %w", costID, err)
			}

			type adjInfo struct {
				MoveID         int64
				ProductID      int64
				AdditionalCost float64
			}
			var adjustments []adjInfo
			for adjRows.Next() {
				var adj adjInfo
				if err := adjRows.Scan(&adj.MoveID, &adj.ProductID, &adj.AdditionalCost); err != nil {
					adjRows.Close()
					return nil, fmt.Errorf("stock.landed.cost: scan adjustment: %w", err)
				}
				adjustments = append(adjustments, adj)
			}
			adjRows.Close()

			for _, adj := range adjustments {
				// Update the corresponding valuation layer remaining_value
				_, err := env.Tx().Exec(env.Ctx(),
					`UPDATE stock_valuation_layer
					 SET remaining_value = remaining_value + $1, value = value + $1
					 WHERE stock_move_id = $2 AND product_id = $3 AND remaining_qty > 0
					 LIMIT 1`,
					adj.AdditionalCost, adj.MoveID, adj.ProductID,
				)
				if err != nil {
					// Non-fatal: layer might not exist yet
					fmt.Printf("stock.landed.cost: update valuation layer for move %d: %v\n", adj.MoveID, err)
				}
			}

			_, err = env.Tx().Exec(env.Ctx(),
				`UPDATE stock_landed_cost SET state = 'done' WHERE id = $1`, costID)
			if err != nil {
				return nil, fmt.Errorf("stock.landed.cost: validate %d: %w", costID, err)
			}
		}
		return true, nil
	})

	// action_cancel: Reverse the landed cost and set state to cancelled.
	// Mirrors: stock.landed.cost.action_cancel()
	m.RegisterMethod("action_cancel", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		for _, costID := range rs.IDs() {
			var state string
			err := env.Tx().QueryRow(env.Ctx(),
				`SELECT state FROM stock_landed_cost WHERE id = $1`, costID,
			).Scan(&state)
			if err != nil {
				return nil, fmt.Errorf("stock.landed.cost: read state for %d: %w", costID, err)
			}

			if state == "done" {
				// Reverse valuation adjustments
				adjRows, err := env.Tx().Query(env.Ctx(),
					`SELECT move_id, product_id, additional_landed_cost
					 FROM stock_valuation_adjustment_lines
					 WHERE cost_id = $1 AND additional_landed_cost != 0`,
					costID,
				)
				if err != nil {
					return nil, fmt.Errorf("stock.landed.cost: query adjustments for cancel %d: %w", costID, err)
				}

				type adjInfo struct {
					MoveID         int64
					ProductID      int64
					AdditionalCost float64
				}
				var adjustments []adjInfo
				for adjRows.Next() {
					var adj adjInfo
					adjRows.Scan(&adj.MoveID, &adj.ProductID, &adj.AdditionalCost)
					adjustments = append(adjustments, adj)
				}
				adjRows.Close()

				for _, adj := range adjustments {
					env.Tx().Exec(env.Ctx(),
						`UPDATE stock_valuation_layer
						 SET remaining_value = remaining_value - $1, value = value - $1
						 WHERE stock_move_id = $2 AND product_id = $3 AND remaining_qty > 0`,
						adj.AdditionalCost, adj.MoveID, adj.ProductID,
					)
				}
			}

			_, err = env.Tx().Exec(env.Ctx(),
				`UPDATE stock_landed_cost SET state = 'cancel' WHERE id = $1`, costID)
			if err != nil {
				return nil, fmt.Errorf("stock.landed.cost: cancel %d: %w", costID, err)
			}
		}
		return true, nil
	})

	// get_cost_summary: Return a summary of landed cost distribution.
	// Mirrors: stock.landed.cost views / reporting
	m.RegisterMethod("get_cost_summary", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		costID := rs.IDs()[0]

		// Get totals by product
		rows, err := env.Tx().Query(env.Ctx(),
			`SELECT val.product_id,
			        COALESCE(SUM(val.former_cost), 0) as total_former,
			        COALESCE(SUM(val.additional_landed_cost), 0) as total_additional,
			        COALESCE(SUM(val.final_cost), 0) as total_final
			 FROM stock_valuation_adjustment_lines val
			 WHERE val.cost_id = $1
			 GROUP BY val.product_id
			 ORDER BY val.product_id`,
			costID,
		)
		if err != nil {
			return nil, fmt.Errorf("stock.landed.cost: query summary for %d: %w", costID, err)
		}
		defer rows.Close()

		var lines []map[string]interface{}
		for rows.Next() {
			var productID int64
			var former, additional, final float64
			if err := rows.Scan(&productID, &former, &additional, &final); err != nil {
				return nil, fmt.Errorf("stock.landed.cost: scan summary row: %w", err)
			}
			lines = append(lines, map[string]interface{}{
				"product_id":             productID,
				"former_cost":            former,
				"additional_landed_cost": additional,
				"final_cost":             final,
			})
		}

		return map[string]interface{}{"summary": lines}, nil
	})

	// --- Sub-models ---

	// stock.landed.cost.lines — individual cost items on a landed cost
	orm.NewModel("stock.landed.cost.lines", orm.ModelOpts{
		Description: "Landed Cost Lines",
	}).AddFields(
		orm.Many2one("cost_id", "stock.landed.cost", orm.FieldOpts{String: "Landed Cost", Required: true, OnDelete: orm.OnDeleteCascade}),
		orm.Char("name", orm.FieldOpts{String: "Description", Required: true}),
		orm.Many2one("product_id", "product.product", orm.FieldOpts{String: "Product"}),
		orm.Float("price_unit", orm.FieldOpts{String: "Cost"}),
		orm.Selection("split_method", []orm.SelectionItem{
			{Value: "equal", Label: "Equal"},
			{Value: "by_quantity", Label: "By Quantity"},
			{Value: "by_current_cost_price", Label: "By Current Cost"},
			{Value: "by_weight", Label: "By Weight"},
			{Value: "by_volume", Label: "By Volume"},
		}, orm.FieldOpts{String: "Split Method", Default: "equal", Required: true}),
		orm.Many2one("account_id", "account.account", orm.FieldOpts{String: "Account"}),
	)

	// stock.valuation.adjustment.lines — per-move cost adjustments
	orm.NewModel("stock.valuation.adjustment.lines", orm.ModelOpts{
		Description: "Valuation Adjustment Lines",
	}).AddFields(
		orm.Many2one("cost_id", "stock.landed.cost", orm.FieldOpts{String: "Landed Cost", Required: true, OnDelete: orm.OnDeleteCascade}),
		orm.Many2one("cost_line_id", "stock.landed.cost.lines", orm.FieldOpts{String: "Cost Line"}),
		orm.Many2one("move_id", "stock.move", orm.FieldOpts{String: "Stock Move"}),
		orm.Many2one("product_id", "product.product", orm.FieldOpts{String: "Product"}),
		orm.Float("quantity", orm.FieldOpts{String: "Quantity"}),
		orm.Float("weight", orm.FieldOpts{String: "Weight"}),
		orm.Float("volume", orm.FieldOpts{String: "Volume"}),
		orm.Monetary("former_cost", orm.FieldOpts{String: "Original Value", CurrencyField: "currency_id"}),
		orm.Monetary("additional_landed_cost", orm.FieldOpts{String: "Additional Cost", CurrencyField: "currency_id"}),
		orm.Monetary("final_cost", orm.FieldOpts{String: "Final Cost", CurrencyField: "currency_id"}),
		orm.Many2one("currency_id", "res.currency", orm.FieldOpts{String: "Currency"}),
	)
}

// roundCurrency rounds a monetary value to 2 decimal places.
func roundCurrency(value float64) float64 {
	return math.Round(value*100) / 100
}
