package models

import (
	"fmt"

	"odoo-go/pkg/orm"
)

// initStockPickingBatch registers stock.picking.batch — batch processing of transfers.
// Mirrors: odoo/addons/stock_picking_batch/models/stock_picking_batch.py
func initStockPickingBatch() {
	m := orm.NewModel("stock.picking.batch", orm.ModelOpts{
		Description: "Batch Transfer",
		Order:       "name desc",
	})
	m.AddFields(
		orm.Char("name", orm.FieldOpts{String: "Name", Default: "New"}),
		orm.Many2one("user_id", "res.users", orm.FieldOpts{String: "Responsible"}),
		orm.Many2one("company_id", "res.company", orm.FieldOpts{String: "Company"}),
		orm.One2many("picking_ids", "stock.picking", "batch_id", orm.FieldOpts{String: "Transfers"}),
		orm.Selection("state", []orm.SelectionItem{
			{Value: "draft", Label: "Draft"},
			{Value: "in_progress", Label: "In Progress"},
			{Value: "done", Label: "Done"},
			{Value: "cancel", Label: "Cancelled"},
		}, orm.FieldOpts{String: "Status", Default: "draft"}),
		orm.Selection("picking_type_code", []orm.SelectionItem{
			{Value: "incoming", Label: "Receipt"},
			{Value: "outgoing", Label: "Delivery"},
			{Value: "internal", Label: "Internal Transfer"},
		}, orm.FieldOpts{String: "Operation Type"}),
		orm.Integer("picking_count", orm.FieldOpts{String: "Transfers Count", Compute: "_compute_picking_count"}),
		orm.Integer("move_line_count", orm.FieldOpts{String: "Move Lines Count", Compute: "_compute_move_line_count"}),
	)

	// _compute_picking_count: Count the number of pickings in this batch.
	// Mirrors: stock.picking.batch._compute_picking_count()
	m.RegisterCompute("picking_count", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		batchID := rs.IDs()[0]
		var count int64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COUNT(*) FROM stock_picking WHERE batch_id = $1`, batchID,
		).Scan(&count)
		return orm.Values{"picking_count": count}, nil
	})

	// _compute_move_line_count: Count the total move lines across all pickings in batch.
	// Mirrors: stock.picking.batch._compute_move_line_count()
	m.RegisterCompute("move_line_count", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		batchID := rs.IDs()[0]
		var count int64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COUNT(*)
			 FROM stock_move_line sml
			 JOIN stock_move sm ON sm.id = sml.move_id
			 JOIN stock_picking sp ON sp.id = sm.picking_id
			 WHERE sp.batch_id = $1`, batchID,
		).Scan(&count)
		return orm.Values{"move_line_count": count}, nil
	})

	// action_confirm: Move batch from draft to in_progress.
	// Mirrors: stock.picking.batch.action_confirm()
	m.RegisterMethod("action_confirm", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		for _, id := range rs.IDs() {
			var state string
			err := env.Tx().QueryRow(env.Ctx(),
				`SELECT state FROM stock_picking_batch WHERE id = $1`, id,
			).Scan(&state)
			if err != nil {
				return nil, fmt.Errorf("stock.picking.batch: read state for %d: %w", id, err)
			}
			if state != "draft" {
				return nil, fmt.Errorf("stock.picking.batch: can only confirm draft batches (batch %d is %q)", id, state)
			}

			// Confirm all draft pickings in batch
			pickRows, err := env.Tx().Query(env.Ctx(),
				`SELECT id FROM stock_picking WHERE batch_id = $1 AND state = 'draft'`, id)
			if err != nil {
				return nil, fmt.Errorf("stock.picking.batch: query draft pickings for %d: %w", id, err)
			}
			var pickIDs []int64
			for pickRows.Next() {
				var pid int64
				pickRows.Scan(&pid)
				pickIDs = append(pickIDs, pid)
			}
			pickRows.Close()

			if len(pickIDs) > 0 {
				pickModel := orm.Registry.Get("stock.picking")
				if pickModel != nil {
					if confirmMethod, ok := pickModel.Methods["action_confirm"]; ok {
						pickRS := env.Model("stock.picking").Browse(pickIDs...)
						if _, err := confirmMethod(pickRS); err != nil {
							return nil, fmt.Errorf("stock.picking.batch: confirm pickings for batch %d: %w", id, err)
						}
					}
				}
			}

			_, err = env.Tx().Exec(env.Ctx(),
				`UPDATE stock_picking_batch SET state = 'in_progress' WHERE id = $1`, id)
			if err != nil {
				return nil, fmt.Errorf("stock.picking.batch: confirm batch %d: %w", id, err)
			}
		}
		return true, nil
	})

	// action_assign: Reserve stock for all pickings in the batch.
	// Mirrors: stock.picking.batch.action_assign()
	m.RegisterMethod("action_assign", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		for _, batchID := range rs.IDs() {
			pickRows, err := env.Tx().Query(env.Ctx(),
				`SELECT id FROM stock_picking WHERE batch_id = $1 AND state IN ('confirmed', 'assigned')`, batchID)
			if err != nil {
				return nil, fmt.Errorf("stock.picking.batch: query pickings for assign %d: %w", batchID, err)
			}
			var pickIDs []int64
			for pickRows.Next() {
				var pid int64
				pickRows.Scan(&pid)
				pickIDs = append(pickIDs, pid)
			}
			pickRows.Close()

			if len(pickIDs) > 0 {
				pickModel := orm.Registry.Get("stock.picking")
				if pickModel != nil {
					if assignMethod, ok := pickModel.Methods["action_assign"]; ok {
						pickRS := env.Model("stock.picking").Browse(pickIDs...)
						if _, err := assignMethod(pickRS); err != nil {
							return nil, fmt.Errorf("stock.picking.batch: assign pickings for batch %d: %w", batchID, err)
						}
					}
				}
			}
		}
		return true, nil
	})

	// action_done: Validate all pickings in the batch, then set batch to done.
	// Mirrors: stock.picking.batch.action_done()
	m.RegisterMethod("action_done", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		for _, batchID := range rs.IDs() {
			var state string
			err := env.Tx().QueryRow(env.Ctx(),
				`SELECT state FROM stock_picking_batch WHERE id = $1`, batchID,
			).Scan(&state)
			if err != nil {
				return nil, fmt.Errorf("stock.picking.batch: read state for %d: %w", batchID, err)
			}
			if state != "in_progress" {
				return nil, fmt.Errorf("stock.picking.batch: can only validate in-progress batches (batch %d is %q)", batchID, state)
			}

			// Validate all non-done pickings
			pickRows, err := env.Tx().Query(env.Ctx(),
				`SELECT id FROM stock_picking WHERE batch_id = $1 AND state != 'done'`, batchID)
			if err != nil {
				return nil, fmt.Errorf("stock.picking.batch: query pickings for done %d: %w", batchID, err)
			}
			var pickIDs []int64
			for pickRows.Next() {
				var pid int64
				pickRows.Scan(&pid)
				pickIDs = append(pickIDs, pid)
			}
			pickRows.Close()

			if len(pickIDs) > 0 {
				pickModel := orm.Registry.Get("stock.picking")
				if pickModel != nil {
					if validateMethod, ok := pickModel.Methods["button_validate"]; ok {
						pickRS := env.Model("stock.picking").Browse(pickIDs...)
						if _, err := validateMethod(pickRS); err != nil {
							return nil, fmt.Errorf("stock.picking.batch: validate pickings for batch %d: %w", batchID, err)
						}
					}
				}
			}

			_, err = env.Tx().Exec(env.Ctx(),
				`UPDATE stock_picking_batch SET state = 'done' WHERE id = $1`, batchID)
			if err != nil {
				return nil, fmt.Errorf("stock.picking.batch: set done for %d: %w", batchID, err)
			}
		}
		return true, nil
	})

	// action_cancel: Cancel the batch and all non-done pickings.
	// Mirrors: stock.picking.batch.action_cancel()
	m.RegisterMethod("action_cancel", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		for _, batchID := range rs.IDs() {
			// Cancel non-done pickings
			pickRows, err := env.Tx().Query(env.Ctx(),
				`SELECT id FROM stock_picking WHERE batch_id = $1 AND state NOT IN ('done', 'cancel')`, batchID)
			if err != nil {
				return nil, fmt.Errorf("stock.picking.batch: query pickings for cancel %d: %w", batchID, err)
			}
			var pickIDs []int64
			for pickRows.Next() {
				var pid int64
				pickRows.Scan(&pid)
				pickIDs = append(pickIDs, pid)
			}
			pickRows.Close()

			if len(pickIDs) > 0 {
				pickModel := orm.Registry.Get("stock.picking")
				if pickModel != nil {
					if cancelMethod, ok := pickModel.Methods["action_cancel"]; ok {
						pickRS := env.Model("stock.picking").Browse(pickIDs...)
						if _, err := cancelMethod(pickRS); err != nil {
							return nil, fmt.Errorf("stock.picking.batch: cancel pickings for batch %d: %w", batchID, err)
						}
					}
				}
			}

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

	// action_print: Generate a summary of the batch for printing.
	// Mirrors: stock.picking.batch print report
	m.RegisterMethod("action_print", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		batchID := rs.IDs()[0]

		rows, err := env.Tx().Query(env.Ctx(),
			`SELECT sp.name as picking_name, sp.state as picking_state,
			    sm.product_id, pt.name as product_name, sm.product_uom_qty,
			    sl_src.name as source_location, sl_dst.name as dest_location
			 FROM stock_picking sp
			 JOIN stock_move sm ON sm.picking_id = sp.id
			 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 sp.batch_id = $1
			 ORDER BY sp.name, pt.name`,
			batchID,
		)
		if err != nil {
			return nil, fmt.Errorf("stock.picking.batch: query print data for %d: %w", batchID, err)
		}
		defer rows.Close()

		var lines []map[string]interface{}
		for rows.Next() {
			var pickingName, pickingState, prodName, srcLoc, dstLoc string
			var prodID int64
			var qty float64
			if err := rows.Scan(&pickingName, &pickingState, &prodID, &prodName, &qty, &srcLoc, &dstLoc); err != nil {
				return nil, fmt.Errorf("stock.picking.batch: scan print row: %w", err)
			}
			lines = append(lines, map[string]interface{}{
				"picking": pickingName, "state": pickingState,
				"product_id": prodID, "product": prodName, "quantity": qty,
				"source": srcLoc, "destination": dstLoc,
			})
		}

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

	// add_pickings: Add pickings to an existing batch.
	// Mirrors: stock.picking.batch add picking wizard
	m.RegisterMethod("add_pickings", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		if len(args) < 1 {
			return nil, fmt.Errorf("stock.picking.batch.add_pickings requires picking_ids")
		}
		pickingIDs, ok := args[0].([]int64)
		if !ok || len(pickingIDs) == 0 {
			return nil, fmt.Errorf("stock.picking.batch: invalid picking_ids")
		}

		env := rs.Env()
		batchID := rs.IDs()[0]

		for _, pid := range pickingIDs {
			_, err := env.Tx().Exec(env.Ctx(),
				`UPDATE stock_picking SET batch_id = $1 WHERE id = $2`, batchID, pid)
			if err != nil {
				return nil, fmt.Errorf("stock.picking.batch: add picking %d to batch %d: %w", pid, batchID, err)
			}
		}

		return true, nil
	})

	// remove_pickings: Remove pickings from the batch.
	// Mirrors: stock.picking.batch remove picking
	m.RegisterMethod("remove_pickings", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		if len(args) < 1 {
			return nil, fmt.Errorf("stock.picking.batch.remove_pickings requires picking_ids")
		}
		pickingIDs, ok := args[0].([]int64)
		if !ok || len(pickingIDs) == 0 {
			return nil, fmt.Errorf("stock.picking.batch: invalid picking_ids")
		}

		env := rs.Env()
		batchID := rs.IDs()[0]

		for _, pid := range pickingIDs {
			_, err := env.Tx().Exec(env.Ctx(),
				`UPDATE stock_picking SET batch_id = NULL WHERE id = $1 AND batch_id = $2`, pid, batchID)
			if err != nil {
				return nil, fmt.Errorf("stock.picking.batch: remove picking %d from batch %d: %w", pid, batchID, err)
			}
		}

		return true, nil
	})
}

// initStockPickingBatchExtension extends stock.picking with batch_id field.
// Mirrors: stock_picking_batch module's extension of stock.picking
func initStockPickingBatchExtension() {
	p := orm.ExtendModel("stock.picking")
	p.AddFields(
		orm.Many2one("batch_id", "stock.picking.batch", orm.FieldOpts{String: "Batch Transfer"}),
	)
}
