package models

import (
	"fmt"
	"strings"

	"odoo-go/pkg/orm"
)

// initAccountReconcileModel registers account.reconcile.model — automatic reconciliation rules.
// Mirrors: odoo/addons/account/models/account_reconcile_model.py
//
// Reconcile models define rules that automatically match bank statement lines
// with open invoices or create write-off entries. Three rule types:
//   - "writeoff_button": manual write-off via button
//   - "writeoff_suggestion": auto-suggest write-off in bank reconciliation
//   - "invoice_matching": auto-match with open invoices based on criteria
func initAccountReconcileModel() {
	m := orm.NewModel("account.reconcile.model", orm.ModelOpts{
		Description: "Reconcile Model",
		Order:       "sequence, id",
	})

	m.AddFields(
		orm.Char("name", orm.FieldOpts{String: "Name", Required: true}),
		orm.Integer("sequence", orm.FieldOpts{String: "Sequence", Default: 10}),
		orm.Many2one("company_id", "res.company", orm.FieldOpts{String: "Company", Required: true}),
		orm.Selection("rule_type", []orm.SelectionItem{
			{Value: "writeoff_button", Label: "Button to generate counterpart entry"},
			{Value: "writeoff_suggestion", Label: "Rule to suggest counterpart entry"},
			{Value: "invoice_matching", Label: "Rule to match invoices/bills"},
		}, orm.FieldOpts{String: "Type", Default: "writeoff_button", Required: true}),
		orm.Boolean("auto_reconcile", orm.FieldOpts{
			String: "Auto-validate",
			Help:   "Validate the statement line automatically if the matched amount is close enough",
		}),
		orm.Boolean("active", orm.FieldOpts{String: "Active", Default: true}),
		// Matching criteria
		orm.Boolean("match_nature", orm.FieldOpts{String: "Amount Nature", Default: true}),
		orm.Selection("match_amount", []orm.SelectionItem{
			{Value: "lower", Label: "Is Lower Than"},
			{Value: "greater", Label: "Is Greater Than"},
			{Value: "between", Label: "Is Between"},
		}, orm.FieldOpts{String: "Amount Condition"}),
		orm.Float("match_amount_min", orm.FieldOpts{String: "Amount Min"}),
		orm.Float("match_amount_max", orm.FieldOpts{String: "Amount Max"}),
		orm.Char("match_label", orm.FieldOpts{
			String: "Label", Help: "Regex pattern to match the bank statement line label",
		}),
		orm.Selection("match_label_param", []orm.SelectionItem{
			{Value: "contains", Label: "Contains"},
			{Value: "not_contains", Label: "Not Contains"},
			{Value: "match_regex", Label: "Match Regex"},
		}, orm.FieldOpts{String: "Label Parameter", Default: "contains"}),
		orm.Char("match_note", orm.FieldOpts{String: "Note", Help: "Match on the statement line notes"}),
		orm.Selection("match_note_param", []orm.SelectionItem{
			{Value: "contains", Label: "Contains"},
			{Value: "not_contains", Label: "Not Contains"},
			{Value: "match_regex", Label: "Match Regex"},
		}, orm.FieldOpts{String: "Note Parameter", Default: "contains"}),
		orm.Many2many("match_journal_ids", "account.journal", orm.FieldOpts{
			String:   "Journals",
			Relation: "reconcile_model_journal_rel",
			Column1:  "model_id",
			Column2:  "journal_id",
		}),
		orm.Many2many("match_partner_ids", "res.partner", orm.FieldOpts{
			String:   "Partners",
			Relation: "reconcile_model_partner_rel",
			Column1:  "model_id",
			Column2:  "partner_id",
		}),
		orm.Selection("match_partner_category_id", []orm.SelectionItem{
			{Value: "customer", Label: "Customer"},
			{Value: "supplier", Label: "Vendor"},
		}, orm.FieldOpts{String: "Partner Category"}),
		orm.Float("match_total_amount_param", orm.FieldOpts{
			String:  "Amount matching %",
			Default: 100,
			Help:    "Percentage of the transaction amount to consider for matching",
		}),
		orm.Boolean("match_same_currency", orm.FieldOpts{
			String: "Same Currency", Default: true,
		}),
		orm.Integer("past_months_limit", orm.FieldOpts{
			String:  "Search Months Limit",
			Default: 18,
			Help:    "Number of months in the past to consider for matching",
		}),
		orm.Float("decimal_separator", orm.FieldOpts{String: "Decimal Separator"}),
		// Write-off lines
		orm.One2many("line_ids", "account.reconcile.model.line", "model_id", orm.FieldOpts{
			String: "Write-off Lines",
		}),
	)

	// apply_rules: try to match a bank statement line against this reconcile model.
	// Mirrors: odoo/addons/account/models/account_reconcile_model.py _apply_rules()
	m.RegisterMethod("apply_rules", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		if len(args) < 1 {
			return nil, fmt.Errorf("account: apply_rules requires a statement line ID")
		}
		env := rs.Env()
		modelID := rs.IDs()[0]

		stLineID, ok := toInt64Arg(args[0])
		if !ok {
			return nil, fmt.Errorf("account: invalid statement line ID")
		}

		// Read model config
		var ruleType string
		var autoReconcile bool
		var matchLabel, matchLabelParam *string
		var matchAmountMin, matchAmountMax float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(rule_type, 'writeoff_button'), COALESCE(auto_reconcile, false),
			        match_label, match_label_param,
			        COALESCE(match_amount_min, 0), COALESCE(match_amount_max, 0)
			 FROM account_reconcile_model WHERE id = $1`, modelID,
		).Scan(&ruleType, &autoReconcile, &matchLabel, &matchLabelParam, &matchAmountMin, &matchAmountMax)

		// Read statement line
		var stAmount float64
		var stLabel, stPaymentRef *string
		var stPartnerID *int64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(amount::float8, 0), payment_ref, narration, partner_id
			 FROM account_bank_statement_line WHERE id = $1`, stLineID,
		).Scan(&stAmount, &stPaymentRef, &stLabel, &stPartnerID)

		// Apply label matching
		if matchLabel != nil && *matchLabel != "" && matchLabelParam != nil {
			labelToCheck := ""
			if stPaymentRef != nil {
				labelToCheck = *stPaymentRef
			}
			if stLabel != nil {
				labelToCheck += " " + *stLabel
			}

			switch *matchLabelParam {
			case "contains":
				if !strings.Contains(strings.ToLower(labelToCheck), strings.ToLower(*matchLabel)) {
					return map[string]interface{}{"matched": false}, nil
				}
			case "not_contains":
				if strings.Contains(strings.ToLower(labelToCheck), strings.ToLower(*matchLabel)) {
					return map[string]interface{}{"matched": false}, nil
				}
			}
		}

		switch ruleType {
		case "invoice_matching":
			return applyInvoiceMatching(env, stLineID, stAmount, stPartnerID, autoReconcile)
		case "writeoff_suggestion":
			return applyWriteoffSuggestion(env, modelID, stLineID, stAmount)
		default:
			return map[string]interface{}{"matched": false, "rule_type": ruleType}, nil
		}
	})

	// -- Reconcile Model Line (write-off definition) --
	rml := orm.NewModel("account.reconcile.model.line", orm.ModelOpts{
		Description: "Reconcile Model Line",
		Order:       "sequence, id",
	})

	rml.AddFields(
		orm.Many2one("model_id", "account.reconcile.model", orm.FieldOpts{
			String: "Model", Required: true, OnDelete: orm.OnDeleteCascade,
		}),
		orm.Many2one("account_id", "account.account", orm.FieldOpts{
			String: "Account", Required: true,
		}),
		orm.Integer("sequence", orm.FieldOpts{String: "Sequence", Default: 10}),
		orm.Char("label", orm.FieldOpts{String: "Journal Item Label"}),
		orm.Selection("amount_type", []orm.SelectionItem{
			{Value: "percentage", Label: "Percentage of balance"},
			{Value: "fixed", Label: "Fixed"},
			{Value: "percentage_st_line", Label: "Percentage of statement line"},
			{Value: "regex", Label: "From label"},
		}, orm.FieldOpts{String: "Amount Type", Default: "percentage", Required: true}),
		orm.Float("amount", orm.FieldOpts{String: "Write-off Amount", Default: 100}),
		orm.Many2many("tax_ids", "account.tax", orm.FieldOpts{
			String:   "Taxes",
			Relation: "reconcile_model_line_tax_rel",
			Column1:  "line_id",
			Column2:  "tax_id",
		}),
		orm.Boolean("force_tax_included", orm.FieldOpts{String: "Tax Included in Price"}),
		orm.Many2one("company_id", "res.company", orm.FieldOpts{
			String: "Company", Related: "model_id.company_id",
		}),
	)
}

// applyInvoiceMatching tries to find an open invoice matching the statement line amount.
// Mirrors: odoo/addons/account/models/account_reconcile_model.py _apply_invoice_matching()
func applyInvoiceMatching(env *orm.Environment, stLineID int64, amount float64, partnerID *int64, autoReconcile bool) (interface{}, error) {
	if partnerID == nil || *partnerID == 0 {
		return map[string]interface{}{"matched": false, "reason": "no partner"}, nil
	}

	absAmount := amount
	if absAmount < 0 {
		absAmount = -absAmount
	}

	// Find open invoices for the partner with matching residual amount
	var matchedMoveLineID int64
	var matchedResidual float64
	err := env.Tx().QueryRow(env.Ctx(),
		`SELECT l.id, ABS(COALESCE(l.amount_residual::float8, 0))
		 FROM account_move_line l
		 JOIN account_move m ON m.id = l.move_id AND m.state = 'posted'
		 JOIN account_account a ON a.id = l.account_id
		 WHERE l.partner_id = $1
		 AND a.account_type IN ('asset_receivable', 'liability_payable')
		 AND ABS(COALESCE(l.amount_residual::float8, 0)) > 0.005
		 AND ABS(ABS(COALESCE(l.amount_residual::float8, 0)) - $2) < $2 * 0.05
		 ORDER BY ABS(ABS(COALESCE(l.amount_residual::float8, 0)) - $2)
		 LIMIT 1`, *partnerID, absAmount,
	).Scan(&matchedMoveLineID, &matchedResidual)

	if err != nil || matchedMoveLineID == 0 {
		return map[string]interface{}{"matched": false, "reason": "no matching invoice"}, nil
	}

	result := map[string]interface{}{
		"matched":       true,
		"move_line_id":  matchedMoveLineID,
		"residual":      matchedResidual,
		"auto_validate": autoReconcile,
	}

	// If auto-reconcile, mark the statement line as reconciled
	if autoReconcile {
		env.Tx().Exec(env.Ctx(),
			`UPDATE account_bank_statement_line SET move_line_id = $1, is_reconciled = true WHERE id = $2`,
			matchedMoveLineID, stLineID)
	}

	return result, nil
}

// applyWriteoffSuggestion suggests a write-off entry based on the reconcile model's lines.
// Mirrors: odoo/addons/account/models/account_reconcile_model.py _apply_writeoff()
func applyWriteoffSuggestion(env *orm.Environment, modelID, stLineID int64, amount float64) (interface{}, error) {
	// Read write-off lines for this model
	rows, err := env.Tx().Query(env.Ctx(),
		`SELECT account_id, COALESCE(label, ''), COALESCE(amount_type, 'percentage'),
		        COALESCE(amount, 100)
		 FROM account_reconcile_model_line
		 WHERE model_id = $1
		 ORDER BY sequence, id`, modelID)
	if err != nil {
		return map[string]interface{}{"matched": false}, nil
	}
	defer rows.Close()

	var suggestions []map[string]interface{}
	for rows.Next() {
		var accountID int64
		var label, amountType string
		var pct float64
		if err := rows.Scan(&accountID, &label, &amountType, &pct); err != nil {
			continue
		}

		writeoffAmount := 0.0
		switch amountType {
		case "percentage":
			writeoffAmount = amount * pct / 100
		case "fixed":
			writeoffAmount = pct
		case "percentage_st_line":
			writeoffAmount = amount * pct / 100
		}

		suggestions = append(suggestions, map[string]interface{}{
			"account_id": accountID,
			"label":      label,
			"amount":     writeoffAmount,
		})
	}

	if len(suggestions) == 0 {
		return map[string]interface{}{"matched": false}, nil
	}

	return map[string]interface{}{
		"matched":     true,
		"rule_type":   "writeoff_suggestion",
		"suggestions": suggestions,
	}, nil
}
