import logging

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

_logger = logging.getLogger(__name__)


class DepositProcessWizard(models.TransientModel):
    _name = 'deposit.deduction.wizard'
    _description = 'Process Security Deposit'

    order_id = fields.Many2one(
        'sale.order',
        string="Rental Order",
        required=True,
        readonly=True,
    )
    partner_id = fields.Many2one(
        related='order_id.partner_id',
        string="Customer",
    )
    deposit_total = fields.Float(
        string="Deposit Amount",
        readonly=True,
    )

    action_type = fields.Selection(
        selection=[
            ('full_refund', "Full Refund"),
            ('partial_refund', "Partial Refund (Deduction for Damages)"),
            ('no_refund', "Full Deduction (Damages Exceed Deposit)"),
            ('sold', "Customer Purchased Rental Product"),
        ],
        string="Action",
        required=True,
        default='full_refund',
    )

    deduction_amount = fields.Float(
        string="Deduction Amount",
        help="Amount to deduct from the security deposit for damages.",
    )
    reason = fields.Text(
        string="Reason",
    )
    remaining_preview = fields.Float(
        string="Remaining to Refund",
        compute='_compute_previews',
    )
    overage_preview = fields.Float(
        string="Additional Invoice Amount",
        compute='_compute_previews',
    )
    refund_preview = fields.Float(
        string="Refund Amount",
        compute='_compute_previews',
    )
    has_card_on_file = fields.Boolean(
        string="Card on File",
        compute='_compute_has_card',
    )

    @api.depends('deposit_total', 'deduction_amount', 'action_type')
    def _compute_previews(self):
        for wizard in self:
            if wizard.action_type == 'full_refund':
                wizard.refund_preview = wizard.deposit_total
                wizard.remaining_preview = wizard.deposit_total
                wizard.overage_preview = 0.0
            elif wizard.action_type == 'sold':
                wizard.refund_preview = wizard.deposit_total
                wizard.remaining_preview = wizard.deposit_total
                wizard.overage_preview = 0.0
            elif wizard.action_type == 'partial_refund':
                diff = wizard.deposit_total - wizard.deduction_amount
                if diff >= 0:
                    wizard.remaining_preview = diff
                    wizard.refund_preview = diff
                    wizard.overage_preview = 0.0
                else:
                    wizard.remaining_preview = 0.0
                    wizard.refund_preview = 0.0
                    wizard.overage_preview = abs(diff)
            elif wizard.action_type == 'no_refund':
                wizard.remaining_preview = 0.0
                wizard.refund_preview = 0.0
                wizard.overage_preview = max(
                    wizard.deduction_amount - wizard.deposit_total, 0.0,
                )
            else:
                wizard.refund_preview = 0.0
                wizard.remaining_preview = 0.0
                wizard.overage_preview = 0.0

    @api.depends('order_id')
    def _compute_has_card(self):
        for wizard in self:
            wizard.has_card_on_file = bool(
                wizard.order_id and wizard.order_id.rental_payment_token_id
            )

    def action_confirm(self):
        """Dispatch to the appropriate deposit processing path."""
        self.ensure_one()
        order = self.order_id

        if self.action_type == 'full_refund':
            return self._process_full_refund(order)
        elif self.action_type == 'partial_refund':
            return self._process_partial_refund(order)
        elif self.action_type == 'no_refund':
            return self._process_no_refund(order)
        elif self.action_type == 'sold':
            return self._process_sold(order)

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

    def _process_full_refund(self, order):
        """Full deposit refund: credit note, Poynt refund, close rental."""
        invoice = order.rental_deposit_invoice_id
        if not invoice:
            raise UserError(_("No deposit invoice found."))

        credit_note = self._create_deposit_credit_note(
            order, invoice, invoice.amount_total,
            _("Security deposit full refund for %s", order.name),
        )

        if credit_note:
            self._process_poynt_refund(order, credit_note)

        order.rental_deposit_status = 'refunded'
        order._send_deposit_refund_email()

        order.message_post(body=_(
            "Security deposit fully refunded: %s",
            self._format_amount(invoice.amount_total, order),
        ))

        self._close_rental(order)
        return {'type': 'ir.actions.act_window_close'}

    def _process_partial_refund(self, order):
        """Partial refund with deduction for damages."""
        if self.deduction_amount <= 0:
            raise UserError(_("Deduction amount must be greater than zero."))
        if not self.reason:
            raise UserError(_("A reason is required for deductions."))

        invoice = order.rental_deposit_invoice_id
        if not invoice:
            raise UserError(_("No deposit invoice found."))

        deposit_total = invoice.amount_total

        if self.deduction_amount >= deposit_total:
            return self._process_no_refund(order)

        refund_amount = deposit_total - self.deduction_amount

        credit_note = self._create_deposit_credit_note(
            order, invoice, refund_amount,
            _("Partial deposit refund for %s (deduction: %s)",
              order.name,
              self._format_amount(self.deduction_amount, order)),
        )

        if credit_note:
            self._process_poynt_refund(order, credit_note)

        order.rental_deposit_status = 'deducted'
        order._send_deposit_refund_email()

        order.message_post(body=_(
            "Security deposit partial refund: %s refunded, %s deducted.\nReason: %s",
            self._format_amount(refund_amount, order),
            self._format_amount(self.deduction_amount, order),
            self.reason,
        ))

        self._close_rental(order)
        return {'type': 'ir.actions.act_window_close'}

    def _process_no_refund(self, order):
        """Full deduction: no refund, create overage invoice if needed."""
        if not self.reason:
            raise UserError(_("A reason is required for deductions."))

        invoice = order.rental_deposit_invoice_id
        deposit_total = invoice.amount_total if invoice else 0.0
        overage = self.deduction_amount - deposit_total if self.deduction_amount > deposit_total else 0.0

        order.rental_deposit_status = 'deducted'

        if overage > 0:
            damage_inv = order._create_damage_invoice(overage)
            if damage_inv and order.rental_payment_token_id:
                ok = order._collect_token_payment_for_invoice(damage_inv)
                if ok:
                    order._send_invoice_with_receipt(damage_inv, 'damage')
                else:
                    order._notify_staff_manual_payment(damage_inv)
            elif damage_inv:
                order._send_invoice_with_receipt(damage_inv, 'damage')

        order.message_post(body=_(
            "Security deposit fully deducted. Deduction: %s.%s\nReason: %s",
            self._format_amount(self.deduction_amount, order),
            _(" Overage invoice: %s", self._format_amount(overage, order)) if overage > 0 else '',
            self.reason,
        ))

        self._close_rental(order)
        return {'type': 'ir.actions.act_window_close'}

    def _process_sold(self, order):
        """Customer purchased the rental: full deposit refund, mark as sold."""
        invoice = order.rental_deposit_invoice_id

        if invoice:
            credit_note = self._create_deposit_credit_note(
                order, invoice, invoice.amount_total,
                _("Deposit refund - customer purchased rental product %s", order.name),
            )
            if credit_note:
                self._process_poynt_refund(order, credit_note)

        order.rental_deposit_status = 'refunded'
        order.rental_purchase_interest = True
        order._send_deposit_refund_email()

        order.message_post(body=_(
            "Customer purchased rental product. Security deposit fully refunded."
        ))

        order.activity_schedule(
            'mail.mail_activity_data_todo',
            date_deadline=fields.Date.today(),
            summary=_("Rental product sold - %s", order.name),
            note=_(
                "Customer %s purchased the rental product from %s. "
                "Process the sale order and schedule delivery/pickup.",
                order.partner_id.name, order.name,
            ),
            user_id=order.user_id.id or self.env.uid,
        )

        self._close_rental(order)
        return {'type': 'ir.actions.act_window_close'}

    def _create_deposit_credit_note(self, order, invoice, amount, ref):
        """Create and post a credit note for the deposit invoice."""
        if not invoice or invoice.payment_state not in ('paid', 'in_payment'):
            _logger.warning(
                "Cannot create credit note: invoice %s state=%s payment=%s",
                invoice.name if invoice else 'None',
                invoice.state if invoice else 'N/A',
                invoice.payment_state if invoice else 'N/A',
            )
            return None

        credit_notes = invoice._reverse_moves(
            default_values_list=[{'ref': ref}],
        )
        if not credit_notes:
            return None

        credit_note = credit_notes[:1]

        if amount != invoice.amount_total:
            for line in credit_note.invoice_line_ids:
                line.price_unit = amount / max(line.quantity, 1)

        credit_note.action_post()
        return credit_note

    def _process_poynt_refund(self, order, credit_note):
        """Process refund via Poynt referenced refund for the credit note."""
        if not credit_note:
            return

        try:
            orig_tx = credit_note._get_original_poynt_transaction()
        except Exception:
            orig_tx = None

        if not orig_tx:
            _logger.warning(
                "No original Poynt transaction for deposit refund on %s. "
                "Credit note created but refund must be processed manually.",
                order.name,
            )
            order._notify_staff_manual_payment(credit_note)
            return

        provider = orig_tx.provider_id.sudo()
        from odoo.addons.fusion_poynt import utils as poynt_utils

        parent_txn_id = orig_tx.poynt_transaction_id
        try:
            txn_data = provider._poynt_make_request(
                'GET', f'transactions/{parent_txn_id}',
            )
            for link in txn_data.get('links', []):
                if link.get('rel') == 'CAPTURE' and link.get('href'):
                    parent_txn_id = link['href']
                    break
        except Exception:
            pass

        minor_amount = poynt_utils.format_poynt_amount(
            abs(credit_note.amount_total), credit_note.currency_id,
        )

        refund_payload = {
            'action': 'REFUND',
            'parentId': parent_txn_id,
            'fundingSource': {'type': 'CREDIT_DEBIT'},
            'amounts': {
                'transactionAmount': minor_amount,
                'orderAmount': minor_amount,
                'currency': credit_note.currency_id.name,
            },
            'context': {
                'source': 'WEB',
                'sourceApp': 'odoo.fusion_rental',
            },
            'notes': f'Deposit refund for {order.name} via {credit_note.name}',
        }

        try:
            result = provider._poynt_make_request(
                'POST', 'transactions', payload=refund_payload,
            )
        except Exception as e:
            _logger.error(
                "Poynt deposit refund failed for %s: %s", order.name, e,
            )
            order._notify_staff_manual_payment(credit_note)
            return

        refund_status = result.get('status', '')
        refund_txn_id = result.get('id', '')

        if refund_status in ('DECLINED', 'FAILED'):
            _logger.warning(
                "Poynt deposit refund declined for %s: %s",
                order.name, refund_status,
            )
            order._notify_staff_manual_payment(credit_note)
            return

        PaymentMethod = self.env['payment.method'].sudo().with_context(
            active_test=False,
        )
        payment_method = PaymentMethod.search(
            [('code', '=', 'card')], limit=1,
        ) or PaymentMethod.search(
            [('code', 'in', ('visa', 'mastercard'))], limit=1,
        )

        refund_tx = self.env['payment.transaction'].sudo().create({
            'provider_id': provider.id,
            'payment_method_id': payment_method.id if payment_method else False,
            'amount': -abs(credit_note.amount_total),
            'currency_id': credit_note.currency_id.id,
            'partner_id': order.partner_id.id,
            'operation': 'refund',
            'source_transaction_id': orig_tx.id,
            'provider_reference': refund_txn_id or '',
            'poynt_transaction_id': refund_txn_id or '',
            'invoice_ids': [(4, credit_note.id)],
        })

        if refund_txn_id and refund_status not in ('PENDING',):
            payment_data = {
                'reference': refund_tx.reference,
                'poynt_transaction_id': refund_txn_id,
                'poynt_status': refund_status,
            }
            refund_tx._process('poynt', payment_data)

        if (
            credit_note.payment_state not in ('paid', 'in_payment')
            and refund_tx.payment_id
        ):
            try:
                (refund_tx.payment_id.move_id.line_ids + credit_note.line_ids).filtered(
                    lambda line: line.account_id == refund_tx.payment_id.destination_account_id
                    and not line.reconciled
                ).reconcile()
            except Exception as e:
                _logger.warning(
                    "Fallback reconciliation failed for %s: %s",
                    order.name, e,
                )

        credit_note.sudo().poynt_refunded = True
        credit_note.sudo().message_post(body=_(
            "Deposit refund processed via Poynt. Amount: %s. "
            "Transaction ID: %s.",
            self._format_amount(abs(credit_note.amount_total), order),
            refund_txn_id or 'N/A',
        ))

    def _close_rental(self, order):
        """Close the rental after deposit processing."""
        if not order.rental_closed:
            try:
                order.action_close_rental()
            except Exception as e:
                _logger.error(
                    "Auto-close after deposit processing failed for %s: %s",
                    order.name, e,
                )

    def _format_amount(self, amount, order):
        return self.env['ir.qweb.field.monetary'].value_to_html(
            amount, {'display_currency': order.currency_id},
        )
