package models

import (
	"fmt"
	"math"
	"time"

	"odoo-go/pkg/orm"
)

// initAccountAsset registers account.asset — fixed asset management with depreciation.
// Mirrors: odoo/addons/account_asset/models/account_asset.py
//
// An asset represents a fixed asset (equipment, vehicle, building, etc.) that is
// depreciated over time. This model tracks the original value, current book value,
// and generates depreciation entries according to the chosen computation method.
func initAccountAsset() {
	m := orm.NewModel("account.asset", orm.ModelOpts{
		Description: "Asset",
		Order:       "acquisition_date desc, id desc",
	})

	// -- Identity --
	m.AddFields(
		orm.Char("name", orm.FieldOpts{String: "Asset Name", Required: true}),
		orm.Selection("asset_type", []orm.SelectionItem{
			{Value: "purchase", Label: "Asset"},
			{Value: "sale", Label: "Deferred Revenue"},
			{Value: "expense", Label: "Deferred Expense"},
		}, orm.FieldOpts{String: "Asset Type", Default: "purchase", Required: true}),
	)

	// -- Accounts --
	m.AddFields(
		orm.Many2one("account_asset_id", "account.account", orm.FieldOpts{
			String: "Asset Account", Help: "Account used to record the purchase of the asset",
		}),
		orm.Many2one("account_depreciation_id", "account.account", orm.FieldOpts{
			String: "Depreciation Account", Help: "Account used in depreciation entries for the depreciation amount",
		}),
		orm.Many2one("account_depreciation_expense_id", "account.account", orm.FieldOpts{
			String: "Expense Account", Help: "Account used in depreciation entries for the expense",
		}),
		orm.Many2one("journal_id", "account.journal", orm.FieldOpts{String: "Journal"}),
		orm.Many2one("company_id", "res.company", orm.FieldOpts{String: "Company", Required: true}),
	)

	// -- Values --
	m.AddFields(
		orm.Monetary("original_value", orm.FieldOpts{String: "Original Value", CurrencyField: "currency_id"}),
		orm.Monetary("book_value", orm.FieldOpts{String: "Book Value", Compute: "_compute_book_value", Store: true, CurrencyField: "currency_id"}),
		orm.Monetary("salvage_value", orm.FieldOpts{String: "Not Depreciable Value", CurrencyField: "currency_id", Default: 0.0}),
		orm.Monetary("value_residual", orm.FieldOpts{String: "Depreciable Value", Compute: "_compute_value_residual", Store: true, CurrencyField: "currency_id"}),
		orm.Many2one("currency_id", "res.currency", orm.FieldOpts{String: "Currency"}),
	)

	// -- Dates --
	m.AddFields(
		orm.Date("acquisition_date", orm.FieldOpts{String: "Acquisition Date"}),
		orm.Date("prorata_date", orm.FieldOpts{String: "Prorata Date", Help: "Prorata temporis start date for first depreciation"}),
		orm.Boolean("prorata_computation_type", orm.FieldOpts{String: "Prorata Computation", Default: false}),
	)

	// -- Depreciation Method --
	m.AddFields(
		orm.Selection("method", []orm.SelectionItem{
			{Value: "linear", Label: "Straight Line"},
			{Value: "degressive", Label: "Declining"},
			{Value: "degressive_then_linear", Label: "Declining then Straight Line"},
		}, orm.FieldOpts{String: "Computation Method", Default: "linear", Required: true}),
		orm.Integer("method_number", orm.FieldOpts{String: "Number of Depreciations", Default: 5}),
		orm.Selection("method_period", []orm.SelectionItem{
			{Value: "1", Label: "Monthly"},
			{Value: "12", Label: "Yearly"},
		}, orm.FieldOpts{String: "Duration", Default: "12"}),
		orm.Float("method_progress_factor", orm.FieldOpts{String: "Declining Factor", Default: 0.3}),
	)

	// -- Status --
	m.AddFields(
		orm.Selection("state", []orm.SelectionItem{
			{Value: "draft", Label: "Draft"},
			{Value: "open", Label: "Running"},
			{Value: "paused", Label: "On Hold"},
			{Value: "close", Label: "Closed"},
		}, orm.FieldOpts{String: "Status", Default: "draft", Required: true}),
		orm.Boolean("active", orm.FieldOpts{String: "Active", Default: true}),
	)

	// -- Related Entries --
	m.AddFields(
		orm.Many2one("original_move_line_ids", "account.move.line", orm.FieldOpts{
			String: "Original Journal Item",
		}),
		orm.One2many("depreciation_move_ids", "account.move", "asset_id", orm.FieldOpts{
			String: "Depreciation Entries",
		}),
		orm.Integer("depreciation_entries_count", orm.FieldOpts{
			String: "Depreciation Entry Count", Compute: "_compute_depreciation_entries_count",
		}),
	)

	// -- Computed fields --

	// _compute_book_value: original - posted depreciation + salvage
	m.RegisterCompute("book_value", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		assetID := rs.IDs()[0]

		var originalValue, salvageValue float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(original_value::float8, 0), COALESCE(salvage_value::float8, 0)
			 FROM account_asset WHERE id = $1`, assetID,
		).Scan(&originalValue, &salvageValue)

		var totalDepreciated float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(SUM(ABS(l.balance)::float8), 0)
			 FROM account_move m
			 JOIN account_move_line l ON l.move_id = m.id
			 WHERE m.asset_id = $1 AND m.state = 'posted'
			 AND l.account_id = (SELECT account_depreciation_expense_id FROM account_asset WHERE id = $1)`,
			assetID,
		).Scan(&totalDepreciated)

		bookValue := originalValue - totalDepreciated
		return orm.Values{"book_value": bookValue}, nil
	})

	// _compute_value_residual: depreciable = original - salvage
	m.RegisterCompute("value_residual", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		assetID := rs.IDs()[0]

		var originalValue, salvageValue float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(original_value::float8, 0), COALESCE(salvage_value::float8, 0)
			 FROM account_asset WHERE id = $1`, assetID,
		).Scan(&originalValue, &salvageValue)

		return orm.Values{"value_residual": originalValue - salvageValue}, nil
	})

	// _compute_depreciation_entries_count
	m.RegisterCompute("depreciation_entries_count", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		assetID := rs.IDs()[0]
		var count int
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COUNT(*) FROM account_move WHERE asset_id = $1`, assetID,
		).Scan(&count)
		return orm.Values{"depreciation_entries_count": count}, nil
	})

	// -- Business Methods --

	// action_set_to_running: draft -> open
	// Mirrors: odoo/addons/account_asset/models/account_asset.py validate()
	m.RegisterMethod("action_set_to_running", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		for _, id := range rs.IDs() {
			var state string
			env.Tx().QueryRow(env.Ctx(),
				`SELECT state FROM account_asset WHERE id = $1`, id).Scan(&state)
			if state != "draft" {
				return nil, fmt.Errorf("account: can only confirm draft assets (current: %s)", state)
			}
			env.Tx().Exec(env.Ctx(),
				`UPDATE account_asset SET state = 'open' WHERE id = $1`, id)
		}
		return true, nil
	})

	// action_set_to_close: open -> close
	// Mirrors: odoo/addons/account_asset/models/account_asset.py set_to_close()
	m.RegisterMethod("action_set_to_close", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		for _, id := range rs.IDs() {
			env.Tx().Exec(env.Ctx(),
				`UPDATE account_asset SET state = 'close' WHERE id = $1`, id)
		}
		return true, nil
	})

	// action_pause: open -> paused
	m.RegisterMethod("action_pause", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		for _, id := range rs.IDs() {
			env.Tx().Exec(env.Ctx(),
				`UPDATE account_asset SET state = 'paused' WHERE id = $1 AND state = 'open'`, id)
		}
		return true, nil
	})

	// action_resume: paused -> open
	m.RegisterMethod("action_resume", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		for _, id := range rs.IDs() {
			env.Tx().Exec(env.Ctx(),
				`UPDATE account_asset SET state = 'open' WHERE id = $1 AND state = 'paused'`, id)
		}
		return true, nil
	})

	// action_set_to_draft: revert to draft
	m.RegisterMethod("action_set_to_draft", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		for _, id := range rs.IDs() {
			env.Tx().Exec(env.Ctx(),
				`UPDATE account_asset SET state = 'draft' WHERE id = $1`, id)
		}
		return true, nil
	})

	// action_compute_depreciation: compute the depreciation schedule.
	// Mirrors: odoo/addons/account_asset/models/account_asset.py compute_depreciation_board()
	//
	// Supports linear (straight-line) and degressive (declining balance) methods.
	// Returns the computed schedule as a list of periods with amounts.
	m.RegisterMethod("action_compute_depreciation", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		assetID := rs.IDs()[0]

		var originalValue, salvageValue, progressFactor float64
		var method, methodPeriod string
		var numPeriods int
		err := env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(original_value::float8, 0), COALESCE(salvage_value::float8, 0),
			        COALESCE(method, 'linear'), COALESCE(method_number, 5),
			        COALESCE(method_period, '12'), COALESCE(method_progress_factor, 0.3)
			 FROM account_asset WHERE id = $1`, assetID,
		).Scan(&originalValue, &salvageValue, &method, &numPeriods, &methodPeriod, &progressFactor)
		if err != nil {
			return nil, fmt.Errorf("account: read asset %d: %w", assetID, err)
		}

		depreciableValue := originalValue - salvageValue
		if numPeriods <= 0 {
			numPeriods = 1
		}
		if depreciableValue <= 0 {
			return map[string]interface{}{"schedule": []map[string]interface{}{}}, nil
		}

		schedule := make([]map[string]interface{}, 0, numPeriods)
		remaining := depreciableValue

		// Determine period length in months
		periodMonths := 12
		if methodPeriod == "1" {
			periodMonths = 1
		}

		startDate := time.Now()

		switch method {
		case "linear":
			periodicAmount := depreciableValue / float64(numPeriods)
			for i := 0; i < numPeriods; i++ {
				amt := periodicAmount
				if i == numPeriods-1 {
					amt = remaining // last period gets remainder to avoid rounding
				}
				remaining -= amt
				depDate := startDate.AddDate(0, periodMonths*(i+1), 0)
				schedule = append(schedule, map[string]interface{}{
					"period":          i + 1,
					"date":            depDate.Format("2006-01-02"),
					"amount":          math.Round(amt*100) / 100,
					"depreciated":     math.Round((depreciableValue-remaining)*100) / 100,
					"remaining_value": math.Round((remaining+salvageValue)*100) / 100,
				})
			}

		case "degressive":
			for i := 0; i < numPeriods; i++ {
				amt := remaining * progressFactor
				if amt < 0.01 {
					amt = remaining
				}
				if i == numPeriods-1 {
					amt = remaining
				}
				remaining -= amt
				depDate := startDate.AddDate(0, periodMonths*(i+1), 0)
				schedule = append(schedule, map[string]interface{}{
					"period":          i + 1,
					"date":            depDate.Format("2006-01-02"),
					"amount":          math.Round(amt*100) / 100,
					"depreciated":     math.Round((depreciableValue-remaining)*100) / 100,
					"remaining_value": math.Round((remaining+salvageValue)*100) / 100,
				})
			}

		case "degressive_then_linear":
			// Use declining balance until it drops below straight-line, then switch
			linearAmount := depreciableValue / float64(numPeriods)
			for i := 0; i < numPeriods; i++ {
				degressiveAmt := remaining * progressFactor
				// Linear amount for remaining periods
				remainingPeriods := numPeriods - i
				linearRemaining := remaining / float64(remainingPeriods)

				amt := degressiveAmt
				if linearRemaining > degressiveAmt {
					amt = linearRemaining // switch to linear
				}
				if amt < linearAmount*0.01 {
					amt = remaining
				}
				if i == numPeriods-1 {
					amt = remaining
				}
				remaining -= amt
				depDate := startDate.AddDate(0, periodMonths*(i+1), 0)
				schedule = append(schedule, map[string]interface{}{
					"period":          i + 1,
					"date":            depDate.Format("2006-01-02"),
					"amount":          math.Round(amt*100) / 100,
					"depreciated":     math.Round((depreciableValue-remaining)*100) / 100,
					"remaining_value": math.Round((remaining+salvageValue)*100) / 100,
				})
			}
		}

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

	// action_create_depreciation_moves: generate actual journal entries for depreciation.
	// Mirrors: odoo/addons/account_asset/models/account_asset.py _generate_moves()
	m.RegisterMethod("action_create_depreciation_moves", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		assetID := rs.IDs()[0]

		var name string
		var journalID, companyID, depAccountID, expenseAccountID int64
		var currencyID *int64
		var state string
		err := env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(name, ''), COALESCE(journal_id, 0), COALESCE(company_id, 0),
			        COALESCE(account_depreciation_id, 0), COALESCE(account_depreciation_expense_id, 0),
			        currency_id, COALESCE(state, 'draft')
			 FROM account_asset WHERE id = $1`, assetID,
		).Scan(&name, &journalID, &companyID, &depAccountID, &expenseAccountID, &currencyID, &state)
		if err != nil {
			return nil, fmt.Errorf("account: read asset %d: %w", assetID, err)
		}

		if state != "open" {
			return nil, fmt.Errorf("account: can only create depreciation moves for running assets")
		}
		if journalID == 0 || depAccountID == 0 || expenseAccountID == 0 {
			return nil, fmt.Errorf("account: asset %d is missing journal or account configuration", assetID)
		}

		// Compute the schedule by calling the registered method
		assetModel := orm.Registry.Get("account.asset")
		if assetModel == nil {
			return nil, fmt.Errorf("account: asset model not registered")
		}
		computeMethod, hasMethod := assetModel.Methods["action_compute_depreciation"]
		if !hasMethod {
			return nil, fmt.Errorf("account: action_compute_depreciation method not found")
		}
		scheduleResult, err := computeMethod(rs)
		if err != nil {
			return nil, err
		}
		scheduleMap, ok := scheduleResult.(map[string]interface{})
		if !ok {
			return nil, fmt.Errorf("account: invalid schedule result")
		}
		scheduleRaw, _ := scheduleMap["schedule"].([]map[string]interface{})
		if len(scheduleRaw) == 0 {
			return nil, fmt.Errorf("account: empty depreciation schedule")
		}

		// Check how many depreciation moves already exist
		var existingCount int
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COUNT(*) FROM account_move WHERE asset_id = $1`, assetID,
		).Scan(&existingCount)

		// Only create the next un-created entry
		if existingCount >= len(scheduleRaw) {
			return nil, fmt.Errorf("account: all depreciation entries already created")
		}

		entry := scheduleRaw[existingCount]
		amount, _ := toFloat(entry["amount"])
		depDate, _ := entry["date"].(string)
		if depDate == "" {
			depDate = time.Now().Format("2006-01-02")
		}

		var curID int64
		if currencyID != nil {
			curID = *currencyID
		} else {
			env.Tx().QueryRow(env.Ctx(),
				`SELECT COALESCE(currency_id, 0) FROM res_company WHERE id = $1`, companyID,
			).Scan(&curID)
		}

		// Create the depreciation journal entry
		moveRS := env.Model("account.move")
		move, err := moveRS.Create(orm.Values{
			"move_type":   "entry",
			"ref":         fmt.Sprintf("Depreciation: %s (%d/%d)", name, existingCount+1, len(scheduleRaw)),
			"date":        depDate,
			"journal_id":  journalID,
			"company_id":  companyID,
			"currency_id": curID,
			"asset_id":    assetID,
		})
		if err != nil {
			return nil, fmt.Errorf("account: create depreciation move: %w", err)
		}

		lineRS := env.Model("account.move.line")
		// Debit: expense account
		if _, err := lineRS.Create(orm.Values{
			"move_id":      move.ID(),
			"account_id":   expenseAccountID,
			"name":         fmt.Sprintf("Depreciation: %s", name),
			"debit":        amount,
			"credit":       0.0,
			"balance":      amount,
			"company_id":   companyID,
			"journal_id":   journalID,
			"currency_id":  curID,
			"display_type": "product",
		}); err != nil {
			return nil, fmt.Errorf("account: create expense line: %w", err)
		}

		// Credit: depreciation account
		if _, err := lineRS.Create(orm.Values{
			"move_id":      move.ID(),
			"account_id":   depAccountID,
			"name":         fmt.Sprintf("Depreciation: %s", name),
			"debit":        0.0,
			"credit":       amount,
			"balance":      -amount,
			"company_id":   companyID,
			"journal_id":   journalID,
			"currency_id":  curID,
			"display_type": "product",
		}); err != nil {
			return nil, fmt.Errorf("account: create depreciation line: %w", err)
		}

		return map[string]interface{}{
			"type":      "ir.actions.act_window",
			"res_model": "account.move",
			"res_id":    move.ID(),
			"view_mode": "form",
			"views":     [][]interface{}{{nil, "form"}},
			"target":    "current",
		}, nil
	})

	// -- DefaultGet --
	m.DefaultGet = func(env *orm.Environment, fields []string) orm.Values {
		vals := orm.Values{
			"acquisition_date": time.Now().Format("2006-01-02"),
			"state":            "draft",
			"method":           "linear",
			"method_number":    5,
			"method_period":    "12",
		}
		companyID := env.CompanyID()
		if companyID > 0 {
			vals["company_id"] = companyID

			var curID int64
			env.Tx().QueryRow(env.Ctx(),
				`SELECT COALESCE(currency_id, 0) FROM res_company WHERE id = $1`, companyID,
			).Scan(&curID)
			if curID > 0 {
				vals["currency_id"] = curID
			}
		}
		return vals
	}

	// Extend account.move with asset_id back-reference
	initAccountMoveAssetExtension()
}

// initAccountMoveAssetExtension adds asset_id to account.move for depreciation entries.
func initAccountMoveAssetExtension() {
	ext := orm.ExtendModel("account.move")
	ext.AddFields(
		orm.Many2one("asset_id", "account.asset", orm.FieldOpts{
			String: "Asset", Help: "Asset linked to this depreciation entry",
		}),
	)
}
