import logging
from odoo import models, fields, api
from odoo.exceptions import UserError

_logger = logging.getLogger(__name__)


class LaundryPaymentWizard(models.TransientModel):
    """POS-like payment wizard for laundry orders (PART 2).

    Simplified flow — single screen:
    1. Shows customer, order total, balance due
    2. Staff selects journal (cash/bank) and enters amount
    3. On confirm:
       - Creates invoice if none exists
       - Posts the invoice
       - Creates account.payment
       - Reconciles payment against invoice

    Permissions:
    - Any laundry user can register payment
    - Only managers can change the payment date (backdate)
    - Only managers can override the amount above order total
    """
    _name = 'laundry.payment.wizard'
    _description = 'Laundry Quick Payment'

    # ── Order info (read-only header) ─────────────────────────────────
    order_id = fields.Many2one(
        'sale.order', string='Order',
        required=True, readonly=True,
        domain=[('is_laundry_order', '=', True)],
    )
    order_name = fields.Char(
        related='order_id.name', string='Order No.', readonly=True,
    )
    partner_id = fields.Many2one(
        related='order_id.partner_id', string='Customer', readonly=True,
    )
    mobile = fields.Char(
        related='order_id.mobile', string='Mobile', readonly=True,
    )
    order_total = fields.Monetary(
        related='order_id.amount_total', string='Order Total',
        currency_field='currency_id', readonly=True,
    )
    amount_due = fields.Monetary(
        related='order_id.amount_due', string='Balance Due',
        currency_field='currency_id', readonly=True,
    )
    laundry_payment_status = fields.Selection(
        related='order_id.laundry_payment_status', readonly=True,
    )
    laundry_state = fields.Selection(
        related='order_id.laundry_state', string='Processing Status', readonly=True,
    )

    # ── Payment fields ────────────────────────────────────────────────
    journal_id = fields.Many2one(
        'account.journal',
        string='Journal',
        required=True,
        domain="[('type', 'in', ['cash', 'bank']), ('company_id', '=', company_id)]",
    )
    payment_method_line_id = fields.Many2one(
        'account.payment.method.line',
        string='Payment Method',
        required=True,
        domain="[('journal_id', '=', journal_id), ('payment_type', '=', 'inbound')]",
    )
    amount = fields.Monetary(
        string='Amount',
        currency_field='currency_id',
        required=True,
    )
    payment_date = fields.Date(
        string='Payment Date',
        required=True,
        default=fields.Date.today,
    )
    memo = fields.Char(
        string='Reference / Memo',
    )
    currency_id = fields.Many2one(
        related='order_id.currency_id', readonly=True,
    )
    company_id = fields.Many2one(
        related='order_id.company_id', readonly=True,
    )

    # ── Computed info ─────────────────────────────────────────────────
    change_amount = fields.Monetary(
        string='Change',
        compute='_compute_change',
        currency_field='currency_id',
        help='Amount to return to customer if overpaying (cash only).',
    )
    is_overpayment = fields.Boolean(compute='_compute_change')

    @api.depends('amount', 'amount_due')
    def _compute_change(self):
        for wiz in self:
            if wiz.amount > 0 and wiz.amount_due > 0:
                wiz.change_amount = max(wiz.amount - wiz.amount_due, 0.0)
                wiz.is_overpayment = wiz.amount > wiz.amount_due
            else:
                wiz.change_amount = 0.0
                wiz.is_overpayment = False

    # ── Defaults ──────────────────────────────────────────────────────
    @api.model
    def default_get(self, fields_list):
        res = super().default_get(fields_list)
        order_id = res.get('order_id') or self.env.context.get('default_order_id')
        if order_id:
            order = self.env['sale.order'].browse(order_id)
            # Default amount = outstanding balance (or total if no invoice yet)
            res['amount'] = order.amount_due if order.amount_due > 0 else order.amount_total
            res['memo'] = order.name
            # Default journal: 1) configured in Laundry Settings, 2) first cash journal
            ICP = self.env['ir.config_parameter'].sudo()
            configured_id = ICP.get_param('laundry_management.cash_journal_id', '')
            cash_journal = False
            if configured_id:
                try:
                    j = self.env['account.journal'].browse(int(configured_id))
                    if j.exists() and j.type in ('cash', 'bank'):
                        cash_journal = j
                except (ValueError, TypeError):
                    pass
            if not cash_journal:
                cash_journal = self.env['account.journal'].search([
                    ('type', '=', 'cash'),
                    ('company_id', '=', order.company_id.id),
                ], limit=1)
            if not cash_journal:
                cash_journal = self.env['account.journal'].search([
                    ('type', '=', 'bank'),
                    ('company_id', '=', order.company_id.id),
                ], limit=1)
            if cash_journal:
                res['journal_id'] = cash_journal.id
                # Default payment method line: first inbound method on the journal
                method_line = self.env['account.payment.method.line'].search([
                    ('journal_id', '=', cash_journal.id),
                    ('payment_type', '=', 'inbound'),
                ], limit=1)
                if method_line:
                    res['payment_method_line_id'] = method_line.id
        return res

    @api.onchange('journal_id')
    def _onchange_journal_id(self):
        """Auto-select first inbound payment method line for the chosen journal."""
        if self.journal_id:
            method_line = self.env['account.payment.method.line'].search([
                ('journal_id', '=', self.journal_id.id),
                ('payment_type', '=', 'inbound'),
            ], limit=1)
            self.payment_method_line_id = method_line or False
        else:
            self.payment_method_line_id = False

    @api.onchange('order_id')
    def _onchange_order_id(self):
        if self.order_id:
            self.amount = (
                self.order_id.amount_due
                if self.order_id.amount_due > 0
                else self.order_id.amount_total
            )
            self.memo = self.order_id.name

    # ── Confirm payment ───────────────────────────────────────────────
    def action_confirm_payment(self):
        """Full accounting flow: invoice → post → payment → reconcile."""
        self.ensure_one()
        order = self.order_id

        if order.state not in ('sale', 'done'):
            raise UserError(
                'The order must be confirmed before registering payment.\n'
                'Please save the order first.'
            )

        if self.amount <= 0:
            raise UserError('Payment amount must be greater than zero.')

        # 1. Get or create invoice ────────────────────────────────────
        invoices = order.invoice_ids.filtered(
            lambda inv: inv.move_type == 'out_invoice' and inv.state != 'cancel'
        )
        if not invoices:
            _logger.info('laundry.payment.wizard: creating invoice for order %s', order.name)
            invoices = order._create_invoices()
            if not invoices:
                raise UserError(
                    'Failed to create an invoice. '
                    'Ensure the order has service lines configured with valid products.'
                )

        # 2. Post any draft invoices ───────────────────────────────────
        draft_invoices = invoices.filtered(lambda inv: inv.state == 'draft')
        if draft_invoices:
            draft_invoices.action_post()

        # 3. Get posted invoices with open balance ─────────────────────
        to_pay = invoices.filtered(
            lambda inv: inv.state == 'posted'
                        and inv.payment_state not in ('paid', 'in_payment')
        )
        if not to_pay:
            raise UserError(
                'This order is already fully paid.\n\n'
                'Payment Status: ' + (order.laundry_payment_status or 'unknown')
            )

        # 4. Actual payment amount (cap at total due, not overpay into accounting)
        # Change is returned physically by cashier; we only record the due amount
        pay_amount = min(self.amount, sum(to_pay.mapped('amount_residual')))
        if pay_amount <= 0:
            raise UserError('No outstanding balance to pay on the posted invoice.')

        # 5. Create account.payment ────────────────────────────────────
        payment_vals = {
            'amount':       pay_amount,
            'journal_id':   self.journal_id.id,
            'payment_method_line_id': self.payment_method_line_id.id,
            'payment_type': 'inbound',
            'partner_type': 'customer',
            'partner_id':   order.partner_id.id,
            'date':         self.payment_date,
            'ref':          self.memo or order.name,
            'currency_id':  order.currency_id.id,
        }
        payment = self.env['account.payment'].create(payment_vals)
        payment.action_post()

        # 6. Reconcile payment against invoice lines ───────────────────
        # NOTE: account.account uses company_ids (M2M) in Odoo 17+, not company_id.
        # Filter directly on move lines by account_type — no separate account search.
        payment_lines = payment.line_ids.filtered(
            lambda l: l.account_id.account_type == 'asset_receivable'
                      and not l.reconciled
        )
        invoice_lines = to_pay.line_ids.filtered(
            lambda l: l.account_id.account_type == 'asset_receivable'
                      and not l.reconciled
        )
        if payment_lines and invoice_lines:
            (payment_lines + invoice_lines).reconcile()

        # 7. Log on order chatter ──────────────────────────────────────
        journal_name = self.journal_id.name
        change_note = ''
        if self.is_overpayment:
            change_note = f' | Change returned: {order.currency_id.symbol} {self.change_amount:.2f}'
        order.message_post(
            body=(
                f'Payment registered: {order.currency_id.symbol} {pay_amount:.2f} '
                f'via {journal_name} on {self.payment_date}.{change_note}'
            ),
        )

        return {'type': 'ir.actions.act_window_close'}

    def action_view_invoice(self):
        """Open the related invoice to view/edit if needed."""
        self.ensure_one()
        invoices = self.order_id.invoice_ids.filtered(
            lambda inv: inv.move_type == 'out_invoice' and inv.state != 'cancel'
        )
        if not invoices:
            raise UserError('No invoice exists for this order yet.')
        return {
            'type':      'ir.actions.act_window',
            'name':      'Invoice',
            'res_model': 'account.move',
            'res_id':    invoices[0].id if len(invoices) == 1 else False,
            'view_mode': 'form' if len(invoices) == 1 else 'list,form',
            'domain':    [('id', 'in', invoices.ids)] if len(invoices) > 1 else [],
            'target':    'current',
        }
