package models

import (
	"fmt"

	"odoo-go/pkg/orm"
)

// initStockReport registers stock.report — transient model for stock quantity reporting.
// Mirrors: odoo/addons/stock/report/stock_report_views.py
func initStockReport() {
	m := orm.NewModel("stock.report", orm.ModelOpts{
		Description: "Stock Report",
		Type:        orm.ModelTransient,
	})
	m.AddFields(
		orm.Many2one("product_id", "product.product", orm.FieldOpts{String: "Product"}),
		orm.Many2one("location_id", "stock.location", orm.FieldOpts{String: "Location"}),
		orm.Date("date_from", orm.FieldOpts{String: "From"}),
		orm.Date("date_to", orm.FieldOpts{String: "To"}),
	)

	// get_stock_data: Aggregate on-hand / reserved / available per product+location.
	// Mirrors: stock.report logic from Odoo stock views.
	m.RegisterMethod("get_stock_data", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()

		query := `
            SELECT p.id, pt.name as product_name, l.name as location_name,
                COALESCE(SUM(q.quantity), 0) as on_hand,
                COALESCE(SUM(q.reserved_quantity), 0) as reserved,
                COALESCE(SUM(q.quantity - q.reserved_quantity), 0) as available
            FROM stock_quant q
            JOIN product_product p ON p.id = q.product_id
            JOIN product_template pt ON pt.id = p.product_tmpl_id
            JOIN stock_location l ON l.id = q.location_id
            WHERE l.usage = 'internal'
            GROUP BY p.id, pt.name, l.name
            ORDER BY pt.name, l.name`

		rows, err := env.Tx().Query(env.Ctx(), query)
		if err != nil {
			return nil, fmt.Errorf("stock.report: query stock data: %w", err)
		}
		defer rows.Close()

		var lines []map[string]interface{}
		for rows.Next() {
			var prodID int64
			var prodName, locName string
			var onHand, reserved, available float64
			if err := rows.Scan(&prodID, &prodName, &locName, &onHand, &reserved, &available); err != nil {
				return nil, fmt.Errorf("stock.report: scan row: %w", err)
			}
			lines = append(lines, map[string]interface{}{
				"product_id": prodID, "product": prodName, "location": locName,
				"on_hand": onHand, "reserved": reserved, "available": available,
			})
		}
		return map[string]interface{}{"lines": lines}, nil
	})

	// get_stock_data_by_product: Aggregate stock for a specific product across all internal locations.
	// Mirrors: stock.report filtered by product
	m.RegisterMethod("get_stock_data_by_product", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		if len(args) < 1 {
			return nil, fmt.Errorf("stock.report.get_stock_data_by_product requires product_id")
		}
		productID, _ := args[0].(int64)
		if productID == 0 {
			return nil, fmt.Errorf("stock.report: invalid product_id")
		}

		env := rs.Env()

		rows, err := env.Tx().Query(env.Ctx(),
			`SELECT l.id, l.complete_name,
			    COALESCE(SUM(q.quantity), 0) as on_hand,
			    COALESCE(SUM(q.reserved_quantity), 0) as reserved,
			    COALESCE(SUM(q.quantity - q.reserved_quantity), 0) as available
			 FROM stock_quant q
			 JOIN stock_location l ON l.id = q.location_id
			 WHERE q.product_id = $1 AND l.usage = 'internal'
			 GROUP BY l.id, l.complete_name
			 ORDER BY l.complete_name`,
			productID,
		)
		if err != nil {
			return nil, fmt.Errorf("stock.report: query by product: %w", err)
		}
		defer rows.Close()

		var lines []map[string]interface{}
		var totalOnHand, totalReserved, totalAvailable float64
		for rows.Next() {
			var locID int64
			var locName string
			var onHand, reserved, available float64
			if err := rows.Scan(&locID, &locName, &onHand, &reserved, &available); err != nil {
				return nil, fmt.Errorf("stock.report: scan by product row: %w", err)
			}
			lines = append(lines, map[string]interface{}{
				"location_id": locID, "location": locName,
				"on_hand": onHand, "reserved": reserved, "available": available,
			})
			totalOnHand += onHand
			totalReserved += reserved
			totalAvailable += available
		}

		return map[string]interface{}{
			"product_id":      productID,
			"lines":           lines,
			"total_on_hand":   totalOnHand,
			"total_reserved":  totalReserved,
			"total_available": totalAvailable,
		}, nil
	})

	// get_stock_data_by_location: Aggregate stock for a specific location across all products.
	// Mirrors: stock.report filtered by location
	m.RegisterMethod("get_stock_data_by_location", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		if len(args) < 1 {
			return nil, fmt.Errorf("stock.report.get_stock_data_by_location requires location_id")
		}
		locationID, _ := args[0].(int64)
		if locationID == 0 {
			return nil, fmt.Errorf("stock.report: invalid location_id")
		}

		env := rs.Env()

		rows, err := env.Tx().Query(env.Ctx(),
			`SELECT p.id, pt.name as product_name,
			    COALESCE(SUM(q.quantity), 0) as on_hand,
			    COALESCE(SUM(q.reserved_quantity), 0) as reserved,
			    COALESCE(SUM(q.quantity - q.reserved_quantity), 0) as available
			 FROM stock_quant q
			 JOIN product_product p ON p.id = q.product_id
			 JOIN product_template pt ON pt.id = p.product_tmpl_id
			 WHERE q.location_id = $1
			 GROUP BY p.id, pt.name
			 ORDER BY pt.name`,
			locationID,
		)
		if err != nil {
			return nil, fmt.Errorf("stock.report: query by location: %w", err)
		}
		defer rows.Close()

		var lines []map[string]interface{}
		var totalOnHand, totalReserved, totalAvailable float64
		for rows.Next() {
			var prodID int64
			var prodName string
			var onHand, reserved, available float64
			if err := rows.Scan(&prodID, &prodName, &onHand, &reserved, &available); err != nil {
				return nil, fmt.Errorf("stock.report: scan by location row: %w", err)
			}
			lines = append(lines, map[string]interface{}{
				"product_id": prodID, "product": prodName,
				"on_hand": onHand, "reserved": reserved, "available": available,
			})
			totalOnHand += onHand
			totalReserved += reserved
			totalAvailable += available
		}

		return map[string]interface{}{
			"location_id":     locationID,
			"lines":           lines,
			"total_on_hand":   totalOnHand,
			"total_reserved":  totalReserved,
			"total_available": totalAvailable,
		}, nil
	})

	// get_move_history: Return stock move history with filters.
	// Mirrors: stock.move.line reporting / traceability
	m.RegisterMethod("get_move_history", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()

		query := `
            SELECT sm.id, sm.name, sm.product_id, pt.name as product_name,
                sm.product_uom_qty, sm.state,
                sl_src.name as source_location, sl_dst.name as dest_location,
                sm.date, sm.origin
            FROM stock_move sm
            JOIN product_product pp ON pp.id = sm.product_id
            JOIN product_template pt ON pt.id = pp.product_tmpl_id
            JOIN stock_location sl_src ON sl_src.id = sm.location_id
            JOIN stock_location sl_dst ON sl_dst.id = sm.location_dest_id
            WHERE sm.state = 'done'
            ORDER BY sm.date DESC
            LIMIT 100`

		rows, err := env.Tx().Query(env.Ctx(), query)
		if err != nil {
			return nil, fmt.Errorf("stock.report: query move history: %w", err)
		}
		defer rows.Close()

		var moves []map[string]interface{}
		for rows.Next() {
			var moveID, productID int64
			var name, productName, state, srcLoc, dstLoc string
			var qty float64
			var date, origin *string
			if err := rows.Scan(&moveID, &name, &productID, &productName, &qty, &state, &srcLoc, &dstLoc, &date, &origin); err != nil {
				return nil, fmt.Errorf("stock.report: scan move history row: %w", err)
			}
			dateStr := ""
			if date != nil {
				dateStr = *date
			}
			originStr := ""
			if origin != nil {
				originStr = *origin
			}
			moves = append(moves, map[string]interface{}{
				"id": moveID, "name": name, "product_id": productID, "product": productName,
				"quantity": qty, "state": state, "source_location": srcLoc,
				"dest_location": dstLoc, "date": dateStr, "origin": originStr,
			})
		}

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

	// get_inventory_valuation: Return total inventory valuation by product.
	// Mirrors: stock report valuation views
	m.RegisterMethod("get_inventory_valuation", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()

		rows, err := env.Tx().Query(env.Ctx(),
			`SELECT p.id, pt.name as product_name,
			    COALESCE(SUM(q.quantity), 0) as total_qty,
			    COALESCE(SUM(q.value), 0) as total_value
			 FROM stock_quant q
			 JOIN product_product p ON p.id = q.product_id
			 JOIN product_template pt ON pt.id = p.product_tmpl_id
			 JOIN stock_location l ON l.id = q.location_id
			 WHERE l.usage = 'internal'
			 GROUP BY p.id, pt.name
			 HAVING SUM(q.quantity) > 0
			 ORDER BY pt.name`,
		)
		if err != nil {
			return nil, fmt.Errorf("stock.report: query valuation: %w", err)
		}
		defer rows.Close()

		var lines []map[string]interface{}
		var grandTotalQty, grandTotalValue float64
		for rows.Next() {
			var prodID int64
			var prodName string
			var totalQty, totalValue float64
			if err := rows.Scan(&prodID, &prodName, &totalQty, &totalValue); err != nil {
				return nil, fmt.Errorf("stock.report: scan valuation row: %w", err)
			}
			avgCost := float64(0)
			if totalQty > 0 {
				avgCost = totalValue / totalQty
			}
			lines = append(lines, map[string]interface{}{
				"product_id": prodID, "product": prodName,
				"quantity": totalQty, "value": totalValue, "average_cost": avgCost,
			})
			grandTotalQty += totalQty
			grandTotalValue += totalValue
		}

		return map[string]interface{}{
			"lines":       lines,
			"total_qty":   grandTotalQty,
			"total_value": grandTotalValue,
		}, nil
	})
}

// initStockForecast registers stock.forecasted.product — transient model for forecast computation.
// Mirrors: odoo/addons/stock/models/stock_forecasted.py
func initStockForecast() {
	m := orm.NewModel("stock.forecasted.product", orm.ModelOpts{
		Description: "Forecasted Stock",
		Type:        orm.ModelTransient,
	})
	m.AddFields(
		orm.Many2one("product_id", "product.product", orm.FieldOpts{String: "Product"}),
	)

	// get_forecast: Compute on-hand, incoming, outgoing and forecast for a product.
	// Mirrors: stock.forecasted.product_product._get_report_data()
	m.RegisterMethod("get_forecast", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		productID := int64(0)
		if len(args) > 0 {
			if p, ok := args[0].(float64); ok {
				productID = int64(p)
			}
		}

		// On hand
		var onHand float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(SUM(quantity - reserved_quantity), 0) FROM stock_quant
             WHERE product_id = $1 AND location_id IN (SELECT id FROM stock_location WHERE usage = 'internal')`,
			productID).Scan(&onHand)

		// Incoming (confirmed moves TO internal locations)
		var incoming float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(SUM(product_uom_qty), 0) FROM stock_move
             WHERE product_id = $1 AND state IN ('confirmed','assigned','waiting')
             AND location_dest_id IN (SELECT id FROM stock_location WHERE usage = 'internal')`,
			productID).Scan(&incoming)

		// Outgoing (confirmed moves FROM internal locations)
		var outgoing float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(SUM(product_uom_qty), 0) FROM stock_move
             WHERE product_id = $1 AND state IN ('confirmed','assigned','waiting')
             AND location_id IN (SELECT id FROM stock_location WHERE usage = 'internal')`,
			productID).Scan(&outgoing)

		return map[string]interface{}{
			"on_hand": onHand, "incoming": incoming, "outgoing": outgoing,
			"forecast": onHand + incoming - outgoing,
		}, nil
	})

	// get_forecast_details: Detailed forecast with move-level breakdown.
	// Mirrors: stock.forecasted.product_product._get_report_lines()
	m.RegisterMethod("get_forecast_details", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		productID := int64(0)
		if len(args) > 0 {
			if p, ok := args[0].(float64); ok {
				productID = int64(p)
			}
		}

		if productID == 0 {
			return nil, fmt.Errorf("stock.forecasted.product: product_id required")
		}

		// On hand
		var onHand float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(SUM(quantity - reserved_quantity), 0) FROM stock_quant
             WHERE product_id = $1 AND location_id IN (SELECT id FROM stock_location WHERE usage = 'internal')`,
			productID).Scan(&onHand)

		// Incoming moves
		inRows, err := env.Tx().Query(env.Ctx(),
			`SELECT sm.id, sm.name, sm.product_uom_qty, sm.date, sm.state,
			        sl.name as source_location, sld.name as dest_location,
			        sp.name as picking_name
			 FROM stock_move sm
			 JOIN stock_location sl ON sl.id = sm.location_id
			 JOIN stock_location sld ON sld.id = sm.location_dest_id
			 LEFT JOIN stock_picking sp ON sp.id = sm.picking_id
			 WHERE sm.product_id = $1 AND sm.state IN ('confirmed','assigned','waiting')
			 AND sm.location_dest_id IN (SELECT id FROM stock_location WHERE usage = 'internal')
			 ORDER BY sm.date`,
			productID,
		)
		if err != nil {
			return nil, fmt.Errorf("stock.forecasted: query incoming moves: %w", err)
		}
		defer inRows.Close()

		var incomingMoves []map[string]interface{}
		var totalIncoming float64
		for inRows.Next() {
			var moveID int64
			var name, state, srcLoc, dstLoc string
			var qty float64
			var date, pickingName *string
			if err := inRows.Scan(&moveID, &name, &qty, &date, &state, &srcLoc, &dstLoc, &pickingName); err != nil {
				return nil, fmt.Errorf("stock.forecasted: scan incoming move: %w", err)
			}
			dateStr := ""
			if date != nil {
				dateStr = *date
			}
			pickStr := ""
			if pickingName != nil {
				pickStr = *pickingName
			}
			incomingMoves = append(incomingMoves, map[string]interface{}{
				"id": moveID, "name": name, "quantity": qty, "date": dateStr,
				"state": state, "source": srcLoc, "destination": dstLoc, "picking": pickStr,
			})
			totalIncoming += qty
		}

		// Outgoing moves
		outRows, err := env.Tx().Query(env.Ctx(),
			`SELECT sm.id, sm.name, sm.product_uom_qty, sm.date, sm.state,
			        sl.name as source_location, sld.name as dest_location,
			        sp.name as picking_name
			 FROM stock_move sm
			 JOIN stock_location sl ON sl.id = sm.location_id
			 JOIN stock_location sld ON sld.id = sm.location_dest_id
			 LEFT JOIN stock_picking sp ON sp.id = sm.picking_id
			 WHERE sm.product_id = $1 AND sm.state IN ('confirmed','assigned','waiting')
			 AND sm.location_id IN (SELECT id FROM stock_location WHERE usage = 'internal')
			 ORDER BY sm.date`,
			productID,
		)
		if err != nil {
			return nil, fmt.Errorf("stock.forecasted: query outgoing moves: %w", err)
		}
		defer outRows.Close()

		var outgoingMoves []map[string]interface{}
		var totalOutgoing float64
		for outRows.Next() {
			var moveID int64
			var name, state, srcLoc, dstLoc string
			var qty float64
			var date, pickingName *string
			if err := outRows.Scan(&moveID, &name, &qty, &date, &state, &srcLoc, &dstLoc, &pickingName); err != nil {
				return nil, fmt.Errorf("stock.forecasted: scan outgoing move: %w", err)
			}
			dateStr := ""
			if date != nil {
				dateStr = *date
			}
			pickStr := ""
			if pickingName != nil {
				pickStr = *pickingName
			}
			outgoingMoves = append(outgoingMoves, map[string]interface{}{
				"id": moveID, "name": name, "quantity": qty, "date": dateStr,
				"state": state, "source": srcLoc, "destination": dstLoc, "picking": pickStr,
			})
			totalOutgoing += qty
		}

		forecast := onHand + totalIncoming - totalOutgoing

		return map[string]interface{}{
			"product_id":     productID,
			"on_hand":        onHand,
			"incoming":       totalIncoming,
			"outgoing":       totalOutgoing,
			"forecast":       forecast,
			"incoming_moves": incomingMoves,
			"outgoing_moves": outgoingMoves,
		}, nil
	})

	// get_forecast_all: Compute forecast for all products with stock or pending moves.
	// Mirrors: stock.forecasted overview
	m.RegisterMethod("get_forecast_all", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()

		rows, err := env.Tx().Query(env.Ctx(),
			`SELECT p.id, pt.name as product_name,
			    COALESCE(oh.on_hand, 0) as on_hand,
			    COALESCE(inc.incoming, 0) as incoming,
			    COALESCE(outg.outgoing, 0) as outgoing
			 FROM product_product p
			 JOIN product_template pt ON pt.id = p.product_tmpl_id
			 LEFT JOIN (
			     SELECT product_id, SUM(quantity - reserved_quantity) as on_hand
			     FROM stock_quant
			     WHERE location_id IN (SELECT id FROM stock_location WHERE usage = 'internal')
			     GROUP BY product_id
			 ) oh ON oh.product_id = p.id
			 LEFT JOIN (
			     SELECT product_id, SUM(product_uom_qty) as incoming
			     FROM stock_move
			     WHERE state IN ('confirmed','assigned','waiting')
			     AND location_dest_id IN (SELECT id FROM stock_location WHERE usage = 'internal')
			     GROUP BY product_id
			 ) inc ON inc.product_id = p.id
			 LEFT JOIN (
			     SELECT product_id, SUM(product_uom_qty) as outgoing
			     FROM stock_move
			     WHERE state IN ('confirmed','assigned','waiting')
			     AND location_id IN (SELECT id FROM stock_location WHERE usage = 'internal')
			     GROUP BY product_id
			 ) outg ON outg.product_id = p.id
			 WHERE COALESCE(oh.on_hand, 0) != 0
			    OR COALESCE(inc.incoming, 0) != 0
			    OR COALESCE(outg.outgoing, 0) != 0
			 ORDER BY pt.name`,
		)
		if err != nil {
			return nil, fmt.Errorf("stock.forecasted: query all forecasts: %w", err)
		}
		defer rows.Close()

		var products []map[string]interface{}
		for rows.Next() {
			var prodID int64
			var prodName string
			var onHand, incoming, outgoing float64
			if err := rows.Scan(&prodID, &prodName, &onHand, &incoming, &outgoing); err != nil {
				return nil, fmt.Errorf("stock.forecasted: scan forecast row: %w", err)
			}
			products = append(products, map[string]interface{}{
				"product_id": prodID, "product": prodName,
				"on_hand": onHand, "incoming": incoming, "outgoing": outgoing,
				"forecast": onHand + incoming - outgoing,
			})
		}

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