# Fusion Accounting - Configuration Settings
# Extends res.config.settings with accounting-specific options
# including fiscal year, deferrals, tax periodicity, and stock accounts.

from datetime import date

from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
from odoo.tools import date_utils
from odoo.tools.misc import format_date

# Shared domain for stock-related account fields
_ACCOUNT_FILTER_DOMAIN = [
    ('account_type', 'not in', (
        'asset_receivable', 'liability_payable',
        'asset_cash', 'liability_credit_card', 'off_balance',
    )),
]


class ResConfigSettings(models.TransientModel):
    _inherit = 'res.config.settings'

    # =====================================================================
    # Fiscal Year
    # =====================================================================

    fiscalyear_last_day = fields.Integer(
        related='company_id.fiscalyear_last_day',
        required=True,
        readonly=False,
    )
    fiscalyear_last_month = fields.Selection(
        related='company_id.fiscalyear_last_month',
        required=True,
        readonly=False,
    )
    group_fiscal_year = fields.Boolean(
        string='Fiscal Years',
        implied_group='fusion_accounting.group_fiscal_year',
    )

    # =====================================================================
    # General Accounting Options
    # =====================================================================

    use_anglo_saxon = fields.Boolean(
        string='Anglo-Saxon Accounting',
        related='company_id.anglo_saxon_accounting',
        readonly=False,
    )
    invoicing_switch_threshold = fields.Date(
        string="Invoicing Switch Threshold",
        related='company_id.invoicing_switch_threshold',
        readonly=False,
    )
    predict_bill_product = fields.Boolean(
        string="Predict Bill Product",
        related='company_id.predict_bill_product',
        readonly=False,
    )

    # =====================================================================
    # Inter-Company Invoice Sync
    # =====================================================================

    fusion_intercompany_invoice_enabled = fields.Boolean(
        string="Inter-Company Invoice Sync",
        related='company_id.fusion_intercompany_invoice_enabled',
        readonly=False,
        help="Automatically create matching bills/invoices between companies.",
    )
    fusion_intercompany_invoice_journal_id = fields.Many2one(
        comodel_name='account.journal',
        string="Inter-Company Journal",
        related='company_id.fusion_intercompany_invoice_journal_id',
        readonly=False,
        help="Default journal for inter-company counter-documents.",
    )

    # =====================================================================
    # Invoice Signing
    # =====================================================================

    sign_invoice = fields.Boolean(
        string='Authorized Signatory on invoice',
        related='company_id.sign_invoice',
        readonly=False,
    )
    signing_user = fields.Many2one(
        comodel_name='res.users',
        string="Signature used to sign all the invoice",
        readonly=False,
        related='company_id.signing_user',
        help="Override every invoice signature with this user's signature.",
    )
    module_sign = fields.Boolean(
        string='Sign',
        compute='_compute_module_sign_status',
    )

    # =====================================================================
    # Deferred Expenses
    # =====================================================================

    deferred_expense_journal_id = fields.Many2one(
        comodel_name='account.journal',
        help='Journal for deferred expense entries.',
        readonly=False,
        related='company_id.deferred_expense_journal_id',
    )
    deferred_expense_account_id = fields.Many2one(
        comodel_name='account.account',
        help='Account for deferred expense balances.',
        readonly=False,
        related='company_id.deferred_expense_account_id',
    )
    generate_deferred_expense_entries_method = fields.Selection(
        related='company_id.generate_deferred_expense_entries_method',
        readonly=False,
        required=True,
        help='When to generate deferred expense entries.',
    )
    deferred_expense_amount_computation_method = fields.Selection(
        related='company_id.deferred_expense_amount_computation_method',
        readonly=False,
        required=True,
        help='How to prorate deferred expense amounts.',
    )

    # =====================================================================
    # Deferred Revenue
    # =====================================================================

    deferred_revenue_journal_id = fields.Many2one(
        comodel_name='account.journal',
        help='Journal for deferred revenue entries.',
        readonly=False,
        related='company_id.deferred_revenue_journal_id',
    )
    deferred_revenue_account_id = fields.Many2one(
        comodel_name='account.account',
        help='Account for deferred revenue balances.',
        readonly=False,
        related='company_id.deferred_revenue_account_id',
    )
    generate_deferred_revenue_entries_method = fields.Selection(
        related='company_id.generate_deferred_revenue_entries_method',
        readonly=False,
        required=True,
        help='When to generate deferred revenue entries.',
    )
    deferred_revenue_amount_computation_method = fields.Selection(
        related='company_id.deferred_revenue_amount_computation_method',
        readonly=False,
        required=True,
        help='How to prorate deferred revenue amounts.',
    )

    # =====================================================================
    # Reporting & Tax Periodicity
    # =====================================================================

    totals_below_sections = fields.Boolean(
        related='company_id.totals_below_sections',
        string='Add totals below sections',
        readonly=False,
        help='Display totals and subtotals beneath report sections.',
    )
    account_tax_periodicity = fields.Selection(
        related='company_id.account_tax_periodicity',
        string='Periodicity',
        readonly=False,
        required=True,
    )
    account_tax_periodicity_reminder_day = fields.Integer(
        related='company_id.account_tax_periodicity_reminder_day',
        string='Reminder',
        readonly=False,
        required=True,
    )
    account_tax_periodicity_journal_id = fields.Many2one(
        related='company_id.account_tax_periodicity_journal_id',
        string='Journal',
        readonly=False,
    )
    account_reports_show_per_company_setting = fields.Boolean(
        compute="_compute_account_reports_show_per_company_setting",
    )

    # =====================================================================
    # Stock Valuation Accounts (Product Category Defaults)
    # =====================================================================

    property_stock_journal = fields.Many2one(
        comodel_name='account.journal',
        string="Stock Journal",
        check_company=True,
        compute='_compute_property_stock_account',
        inverse='_set_property_stock_journal',
    )
    property_account_income_categ_id = fields.Many2one(
        comodel_name='account.account',
        string="Income Account",
        check_company=True,
        domain=_ACCOUNT_FILTER_DOMAIN,
        compute='_compute_property_stock_account',
        inverse='_set_property_account_income_categ_id',
    )
    property_account_expense_categ_id = fields.Many2one(
        comodel_name='account.account',
        string="Expense Account",
        check_company=True,
        domain=_ACCOUNT_FILTER_DOMAIN,
        compute='_compute_property_stock_account',
        inverse='_set_property_account_expense_categ_id',
    )
    property_stock_valuation_account_id = fields.Many2one(
        comodel_name='account.account',
        string="Stock Valuation Account",
        check_company=True,
        domain="[]",
        compute='_compute_property_stock_account',
        inverse='_set_property_stock_valuation_account_id',
    )
    property_stock_account_input_categ_id = fields.Many2one(
        comodel_name='account.account',
        string="Stock Input Account",
        check_company=True,
        domain="[]",
        compute='_compute_property_stock_account',
        inverse='_set_property_stock_account_input_categ_id',
    )
    property_stock_account_output_categ_id = fields.Many2one(
        comodel_name='account.account',
        string="Stock Output Account",
        check_company=True,
        domain="[]",
        compute='_compute_property_stock_account',
        inverse='_set_property_stock_account_output_categ_id',
    )

    # =====================================================================
    # Compute Methods
    # =====================================================================

    @api.depends('sign_invoice')
    def _compute_module_sign_status(self):
        """Check whether the Sign module is installed or sign is enabled."""
        is_sign_installed = 'sign' in self.env['ir.module.module']._installed()
        for cfg in self:
            cfg.module_sign = is_sign_installed or cfg.company_id.sign_invoice

    @api.depends('company_id')
    def _compute_account_reports_show_per_company_setting(self):
        """Show per-company tax report start date settings when the
        company operates in countries with custom start dates."""
        custom_codes = self._get_country_codes_with_another_tax_closing_start_date()
        relevant_countries = (
            self.env['account.fiscal.position'].search([
                ('company_id', '=', self.env.company.id),
                ('foreign_vat', '!=', False),
            ]).mapped('country_id')
            + self.env.company.account_fiscal_country_id
        )
        for cfg in self:
            cfg.account_reports_show_per_company_setting = bool(
                set(relevant_countries.mapped('code')) & custom_codes
            )

    @api.depends('company_id')
    def _compute_property_stock_account(self):
        """Load stock account defaults from product.category properties."""
        prop_names = self._get_account_stock_properties_names()
        ProdCat = self.env['product.category']
        for cfg in self:
            scoped = cfg.with_company(cfg.company_id)
            for prop_name in prop_names:
                fld = ProdCat._fields[prop_name]
                scoped[prop_name] = fld.get_company_dependent_fallback(ProdCat)

    # =====================================================================
    # Constraints
    # =====================================================================

    @api.constrains('fiscalyear_last_day', 'fiscalyear_last_month')
    def _check_fiscalyear(self):
        """Validate that the fiscal year end date is a real calendar date."""
        for cfg in self:
            try:
                date(2020, int(cfg.fiscalyear_last_month), cfg.fiscalyear_last_day)
            except ValueError:
                raise ValidationError(_(
                    'Invalid fiscal year date: day %(day)s is out of range '
                    'for month %(month)s.',
                    month=cfg.fiscalyear_last_month,
                    day=cfg.fiscalyear_last_day,
                ))

    # =====================================================================
    # Create Override
    # =====================================================================

    @api.model_create_multi
    def create(self, vals_list):
        """Write fiscal year settings atomically to the company to
        avoid intermediate constraint violations from related fields."""
        for vals in vals_list:
            fy_day = vals.pop('fiscalyear_last_day', False) or self.env.company.fiscalyear_last_day
            fy_month = vals.pop('fiscalyear_last_month', False) or self.env.company.fiscalyear_last_month
            company_updates = {}
            if fy_day != self.env.company.fiscalyear_last_day:
                company_updates['fiscalyear_last_day'] = fy_day
            if fy_month != self.env.company.fiscalyear_last_month:
                company_updates['fiscalyear_last_month'] = fy_month
            if company_updates:
                self.env.company.write(company_updates)
        return super().create(vals_list)

    # =====================================================================
    # Actions
    # =====================================================================

    def open_tax_group_list(self):
        """Open the tax group list filtered by the fiscal country."""
        self.ensure_one()
        return {
            'type': 'ir.actions.act_window',
            'name': 'Tax groups',
            'res_model': 'account.tax.group',
            'view_mode': 'list',
            'context': {
                'default_country_id': self.account_fiscal_country_id.id,
                'search_default_country_id': self.account_fiscal_country_id.id,
            },
        }

    def open_company_dependent_report_settings(self):
        """Open the report configuration view for per-company start dates."""
        self.ensure_one()
        base_report = self.env.ref('account.generic_tax_report')
        variant_reports = base_report._get_variants(base_report.id)
        return {
            'type': 'ir.actions.act_window',
            'name': _('Configure your start dates'),
            'res_model': 'account.report',
            'domain': [('id', 'in', variant_reports.ids)],
            'views': [(
                self.env.ref(
                    'fusion_accounting.account_report_tree_configure_start_dates',
                ).id,
                'list',
            )],
        }

    # =====================================================================
    # Hooks
    # =====================================================================

    def _get_country_codes_with_another_tax_closing_start_date(self):
        """Hook for localization modules to declare countries with
        custom tax closing start date support.

        :return: Set of country code strings.
        """
        return set()

    # =====================================================================
    # Stock Property Setters
    # =====================================================================

    def _set_property_stock_journal(self):
        for cfg in self:
            cfg._persist_product_category_default('property_stock_journal')

    def _set_property_account_income_categ_id(self):
        for cfg in self:
            cfg._persist_product_category_default('property_account_income_categ_id')

    def _set_property_account_expense_categ_id(self):
        for cfg in self:
            cfg._persist_product_category_default('property_account_expense_categ_id')

    def _set_property_stock_valuation_account_id(self):
        for cfg in self:
            cfg._persist_product_category_default('property_stock_valuation_account_id')

    def _set_property_stock_account_input_categ_id(self):
        for cfg in self:
            cfg._persist_product_category_default('property_stock_account_input_categ_id')

    def _set_property_stock_account_output_categ_id(self):
        for cfg in self:
            cfg._persist_product_category_default('property_stock_account_output_categ_id')

    def _persist_product_category_default(self, field_name):
        """Save a product category default value via ir.default."""
        self.env['ir.default'].set(
            'product.category', field_name,
            self[field_name].id,
            company_id=self.company_id.id,
        )

    @api.model
    def _get_account_stock_properties_names(self):
        """Return the list of stock-related property field names."""
        return [
            'property_stock_journal',
            'property_account_income_categ_id',
            'property_account_expense_categ_id',
            'property_stock_valuation_account_id',
            'property_stock_account_input_categ_id',
            'property_stock_account_output_categ_id',
        ]
