"""
Fusion Accounting - Integration Bridge Modules
===============================================

Provides optional glue code between Fusion Accounting and other Odoo
applications.  Each bridge class extends a core accounting model with
fields and methods that only become meaningful when the target module
(fleet, hr_expense, helpdesk) is installed.

All dependencies are **soft**: the bridges use ``try/except ImportError``
guards so that Fusion Accounting installs and operates normally even
when the partner modules are absent.

Bridges
-------
* **FusionFleetBridge** -- tags journal-item expenses to fleet vehicles.
* **FusionExpenseBridge** -- creates journal entries from approved
  HR expense sheets.
* **FusionHelpdeskBridge** -- generates credit notes linked to helpdesk
  tickets for rapid customer resolution.

Copyright (c) Nexa Systems Inc. - All rights reserved.
"""

import logging

from odoo import api, fields, models, Command, _
from odoo.exceptions import UserError, ValidationError

_logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Soft-dependency probes
# ---------------------------------------------------------------------------
# Each flag is True only when the corresponding Odoo module is importable.
# The flags are evaluated at *module-load* time, so they reflect the state
# of the Odoo installation rather than the database registry.

_fleet_available = False
try:
    from odoo.addons.fleet import models as _fleet_models  # noqa: F401
    _fleet_available = True
except ImportError:
    _logger.debug("fleet module not available -- FusionFleetBridge will be inert.")

_hr_expense_available = False
try:
    from odoo.addons.hr_expense import models as _hr_expense_models  # noqa: F401
    _hr_expense_available = True
except ImportError:
    _logger.debug("hr_expense module not available -- FusionExpenseBridge will be inert.")

_helpdesk_available = False
try:
    from odoo.addons.helpdesk import models as _helpdesk_models  # noqa: F401
    _helpdesk_available = True
except ImportError:
    _logger.debug("helpdesk module not available -- FusionHelpdeskBridge will be inert.")


# ═══════════════════════════════════════════════════════════════════════════
# Fleet Bridge
# ═══════════════════════════════════════════════════════════════════════════

class FusionFleetBridge(models.Model):
    """Extends journal items so each line can optionally reference a fleet
    vehicle, enabling per-vehicle cost tracking and reporting.

    When the *fleet* module is **not** installed the ``fusion_vehicle_id``
    field is still created (as an orphan Many2one) but it will never
    resolve, and the UI hides it via conditional visibility.
    """

    _name = "account.move.line"
    _inherit = "account.move.line"

    # ---- Fields ----
    fusion_vehicle_id = fields.Many2one(
        comodel_name="fleet.vehicle",
        string="Vehicle",
        index="btree_not_null",
        ondelete="set null",
        copy=True,
        help=(
            "Optionally link this journal item to a fleet vehicle for "
            "per-vehicle expense tracking and cost-center analysis."
        ),
    )
    fusion_vehicle_license_plate = fields.Char(
        related="fusion_vehicle_id.license_plate",
        string="License Plate",
        readonly=True,
        store=False,
    )

    # ---- Helpers ----
    def _fusion_is_fleet_installed(self):
        """Runtime check: is the *fleet* model actually registered?"""
        return "fleet.vehicle" in self.env

    @api.onchange("fusion_vehicle_id")
    def _onchange_fusion_vehicle_id(self):
        """When a vehicle is selected, suggest the vehicle's display name
        as the line label if the label is currently empty."""
        for line in self:
            if line.fusion_vehicle_id and not line.name:
                line.name = _("Expense: %s", line.fusion_vehicle_id.display_name)


# ═══════════════════════════════════════════════════════════════════════════
# HR Expense Bridge
# ═══════════════════════════════════════════════════════════════════════════

class FusionExpenseBridge(models.Model):
    """Links journal entries to approved HR expense sheets and provides a
    method to generate accounting entries from those sheets.

    When *hr_expense* is **not** installed the field and method remain on
    the model but are functionally inert.
    """

    _name = "account.move"
    _inherit = "account.move"

    # ---- Fields ----
    fusion_expense_sheet_id = fields.Many2one(
        comodel_name="hr.expense.sheet",
        string="Expense Report",
        index="btree_not_null",
        ondelete="set null",
        copy=False,
        readonly=True,
        help=(
            "The HR expense sheet from which this journal entry was "
            "generated.  Populated automatically by the bridge."
        ),
    )
    fusion_expense_employee_id = fields.Many2one(
        related="fusion_expense_sheet_id.employee_id",
        string="Expense Employee",
        readonly=True,
        store=False,
    )

    # ---- Helpers ----
    def _fusion_is_hr_expense_installed(self):
        """Runtime check: is the *hr.expense.sheet* model registered?"""
        return "hr.expense.sheet" in self.env

    # ---- Actions ----
    def action_open_expense_sheet(self):
        """Navigate to the linked expense sheet form."""
        self.ensure_one()
        if not self.fusion_expense_sheet_id:
            raise UserError(_("No expense report is linked to this entry."))
        return {
            "type": "ir.actions.act_window",
            "res_model": "hr.expense.sheet",
            "res_id": self.fusion_expense_sheet_id.id,
            "view_mode": "form",
            "target": "current",
        }

    # ---- Core method: create journal entry from expense sheet ----
    @api.model
    def create_move_from_expense_sheet(self, expense_sheet_id):
        """Generate a journal entry from an approved HR expense sheet.

        :param int expense_sheet_id: id of the ``hr.expense.sheet`` record.
        :returns: the newly created ``account.move`` recordset.
        :raises UserError: if the expense sheet is not in *approve* state,
            or if the hr_expense module is not installed.
        """
        if not self._fusion_is_hr_expense_installed():
            raise UserError(
                _("The HR Expense module is not installed.  "
                  "Please install it before creating entries from expense sheets.")
            )

        sheet = self.env["hr.expense.sheet"].browse(expense_sheet_id)
        if not sheet.exists():
            raise UserError(_("Expense sheet #%d does not exist.", expense_sheet_id))

        if sheet.state != "approve":
            raise UserError(
                _("Only approved expense reports can be converted to journal "
                  "entries.  Current status: %s.", sheet.state)
            )

        # Determine the journal -- prefer the company's expense journal,
        # fall back to the first available miscellaneous journal.
        journal = self.env["account.journal"].search(
            [
                ("company_id", "=", sheet.company_id.id),
                ("type", "=", "purchase"),
            ],
            limit=1,
        )
        if not journal:
            journal = self.env["account.journal"].search(
                [
                    ("company_id", "=", sheet.company_id.id),
                    ("type", "=", "general"),
                ],
                limit=1,
            )
        if not journal:
            raise UserError(
                _("No suitable purchase or miscellaneous journal found for "
                  "company %s.", sheet.company_id.name)
            )

        # Build move-line values from each expense line.
        move_line_vals = []
        total_amount = 0.0

        for expense in sheet.expense_line_ids:
            amount = expense.total_amount_company
            total_amount += amount

            # Debit: expense account
            move_line_vals.append(Command.create({
                "name": expense.name or _("Expense: %s", sheet.name),
                "account_id": expense.account_id.id,
                "debit": amount if amount > 0 else 0.0,
                "credit": -amount if amount < 0 else 0.0,
                "partner_id": sheet.employee_id.work_contact_id.id
                    if sheet.employee_id.work_contact_id else False,
                "analytic_distribution": expense.analytic_distribution or False,
            }))

        # Credit: payable account (employee)
        payable_account = (
            sheet.employee_id.work_contact_id.property_account_payable_id
            if sheet.employee_id.work_contact_id
            else self.env["account.account"].search(
                [
                    ("company_id", "=", sheet.company_id.id),
                    ("account_type", "=", "liability_payable"),
                ],
                limit=1,
            )
        )
        if not payable_account:
            raise UserError(
                _("No payable account found for employee %s.",
                  sheet.employee_id.name)
            )

        move_line_vals.append(Command.create({
            "name": _("Payable: %s", sheet.name),
            "account_id": payable_account.id,
            "debit": -total_amount if total_amount < 0 else 0.0,
            "credit": total_amount if total_amount > 0 else 0.0,
            "partner_id": sheet.employee_id.work_contact_id.id
                if sheet.employee_id.work_contact_id else False,
        }))

        move = self.create({
            "journal_id": journal.id,
            "date": fields.Date.context_today(self),
            "ref": _("Expense Report: %s", sheet.name),
            "fusion_expense_sheet_id": sheet.id,
            "move_type": "entry",
            "line_ids": move_line_vals,
        })

        _logger.info(
            "Fusion Expense Bridge: created journal entry %s (id=%d) "
            "from expense sheet '%s' (id=%d).",
            move.name, move.id, sheet.name, sheet.id,
        )

        return move


# ═══════════════════════════════════════════════════════════════════════════
# Helpdesk Bridge
# ═══════════════════════════════════════════════════════════════════════════

class FusionHelpdeskBridge(models.Model):
    """Extends journal entries with helpdesk-ticket linkage and provides
    an action to create a credit note directly from a ticket.

    When *helpdesk* is **not** installed the fields remain on the model
    but are functionally inert, and the UI hides the button.
    """

    _name = "account.move"
    _inherit = "account.move"

    # ---- Fields ----
    fusion_helpdesk_ticket_id = fields.Many2one(
        comodel_name="helpdesk.ticket",
        string="Helpdesk Ticket",
        index="btree_not_null",
        ondelete="set null",
        copy=False,
        help=(
            "The helpdesk ticket associated with this credit note.  "
            "Set automatically when a credit note is created from a ticket."
        ),
    )
    fusion_helpdesk_ticket_ref = fields.Char(
        related="fusion_helpdesk_ticket_id.name",
        string="Ticket Reference",
        readonly=True,
        store=False,
    )

    # ---- Helpers ----
    def _fusion_is_helpdesk_installed(self):
        """Runtime check: is the *helpdesk.ticket* model registered?"""
        return "helpdesk.ticket" in self.env

    # ---- Actions ----
    def action_open_helpdesk_ticket(self):
        """Navigate to the linked helpdesk ticket form."""
        self.ensure_one()
        if not self.fusion_helpdesk_ticket_id:
            raise UserError(_("No helpdesk ticket is linked to this entry."))
        return {
            "type": "ir.actions.act_window",
            "res_model": "helpdesk.ticket",
            "res_id": self.fusion_helpdesk_ticket_id.id,
            "view_mode": "form",
            "target": "current",
        }

    @api.model
    def action_create_credit_note_from_ticket(self, ticket_id, invoice_id=None):
        """Create a credit note linked to a helpdesk ticket.

        If *invoice_id* is provided the credit note reverses that specific
        invoice.  Otherwise a standalone credit note is created with the
        ticket's partner and a reference back to the ticket.

        :param int ticket_id: id of the ``helpdesk.ticket`` record.
        :param int|None invoice_id: optional id of the invoice to reverse.
        :returns: window action pointing to the new credit note form.
        :raises UserError: if the helpdesk module is not installed.
        """
        if not self._fusion_is_helpdesk_installed():
            raise UserError(
                _("The Helpdesk module is not installed.  "
                  "Please install it before creating credit notes from tickets.")
            )

        Ticket = self.env["helpdesk.ticket"]
        ticket = Ticket.browse(ticket_id)
        if not ticket.exists():
            raise UserError(_("Helpdesk ticket #%d does not exist.", ticket_id))

        partner = ticket.partner_id
        if not partner:
            raise UserError(
                _("Ticket '%s' has no customer set.  A customer is required "
                  "to create a credit note.", ticket.name)
            )

        # ---- Path A: reverse an existing invoice ----
        if invoice_id:
            invoice = self.browse(invoice_id)
            if not invoice.exists():
                raise UserError(_("Invoice #%d does not exist.", invoice_id))

            if invoice.move_type not in ("out_invoice", "out_receipt"):
                raise UserError(
                    _("Only customer invoices can be reversed from a "
                      "helpdesk ticket.")
                )

            # Use the standard reversal wizard logic.
            reversal_vals = {
                "journal_id": invoice.journal_id.id,
                "date": fields.Date.context_today(self),
                "reason": _("Credit note from ticket: %s", ticket.name),
            }
            credit_note = invoice._reverse_moves(
                default_values_list=[reversal_vals],
                cancel=False,
            )
            credit_note.write({
                "fusion_helpdesk_ticket_id": ticket.id,
                "ref": _("Ticket: %s", ticket.name),
            })

        # ---- Path B: create a blank credit note ----
        else:
            journal = self.env["account.journal"].search(
                [
                    ("company_id", "=", ticket.company_id.id or self.env.company.id),
                    ("type", "=", "sale"),
                ],
                limit=1,
            )
            if not journal:
                raise UserError(
                    _("No sales journal found.  Please configure one before "
                      "creating credit notes.")
                )

            credit_note = self.create({
                "move_type": "out_refund",
                "journal_id": journal.id,
                "partner_id": partner.id,
                "date": fields.Date.context_today(self),
                "ref": _("Ticket: %s", ticket.name),
                "narration": _(
                    "Credit note generated from helpdesk ticket "
                    "'%s' (ID %d).", ticket.name, ticket.id,
                ),
                "fusion_helpdesk_ticket_id": ticket.id,
            })

        _logger.info(
            "Fusion Helpdesk Bridge: created credit note %s (id=%d) "
            "from ticket '%s' (id=%d).",
            credit_note.name, credit_note.id, ticket.name, ticket.id,
        )

        return {
            "type": "ir.actions.act_window",
            "res_model": "account.move",
            "res_id": credit_note.id,
            "view_mode": "form",
            "target": "current",
        }
