# Part of Odoo. See LICENSE file for full copyright and licensing details.

import json
import logging

from datetime import timedelta

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

from odoo.addons.fusion_poynt import utils as poynt_utils

_logger = logging.getLogger(__name__)

REFERENCED_REFUND_LIMIT_DAYS = 180


class PoyntRefundWizard(models.TransientModel):
    _name = 'poynt.refund.wizard'
    _description = 'Refund via Poynt'

    credit_note_id = fields.Many2one(
        'account.move',
        string="Credit Note",
        required=True,
        readonly=True,
    )
    original_invoice_id = fields.Many2one(
        'account.move',
        string="Original Invoice",
        readonly=True,
    )
    partner_id = fields.Many2one(
        related='credit_note_id.partner_id',
        string="Customer",
    )
    amount = fields.Monetary(
        string="Refund Amount",
        required=True,
        currency_field='currency_id',
    )
    currency_id = fields.Many2one(
        'res.currency',
        string="Currency",
        required=True,
        readonly=True,
    )
    provider_id = fields.Many2one(
        'payment.provider',
        string="Poynt Provider",
        required=True,
        readonly=True,
    )
    provider_name = fields.Char(
        related='provider_id.name',
        string="Poynt Provider",
        readonly=True,
    )
    original_transaction_id = fields.Many2one(
        'payment.transaction',
        string="Original Transaction",
        readonly=True,
    )
    original_poynt_txn_id = fields.Char(
        string="Poynt Transaction ID",
        readonly=True,
    )
    card_info = fields.Char(
        string="Card Used",
        readonly=True,
    )
    transaction_age_days = fields.Integer(
        string="Transaction Age (days)",
        readonly=True,
    )
    refund_type = fields.Selection(
        selection=[
            ('referenced', "Referenced Refund"),
            ('non_referenced', "Non-Referenced Credit"),
        ],
        string="Refund Method",
        readonly=True,
    )
    refund_type_note = fields.Text(
        string="Note",
        readonly=True,
    )

    terminal_id = fields.Many2one(
        'poynt.terminal',
        string="Terminal",
        domain="[('provider_id', '=', provider_id), ('active', '=', True)]",
        help="Terminal to process the non-referenced credit on.",
    )

    refund_transaction_id = fields.Many2one(
        'payment.transaction',
        string="Refund Transaction",
        readonly=True,
    )

    state = fields.Selection(
        selection=[
            ('confirm', "Confirm"),
            ('done', "Refunded"),
            ('error', "Error"),
        ],
        default='confirm',
    )
    status_message = fields.Text(string="Status", readonly=True)

    def _get_provider_sudo(self):
        return self.provider_id.sudo()

    @api.model
    def default_get(self, fields_list):
        res = super().default_get(fields_list)
        credit_note_id = self.env.context.get('active_id')
        active_model = self.env.context.get('active_model')

        if active_model != 'account.move' or not credit_note_id:
            return res

        credit_note = self.env['account.move'].browse(credit_note_id)
        res['credit_note_id'] = credit_note.id
        res['amount'] = abs(credit_note.amount_residual) or abs(credit_note.amount_total)
        res['currency_id'] = credit_note.currency_id.id

        orig_tx = credit_note._get_original_poynt_transaction()
        if not orig_tx:
            raise UserError(_(
                "No Poynt payment transaction found for the original invoice. "
                "This credit note cannot be refunded via Poynt."
            ))

        res['original_transaction_id'] = orig_tx.id
        res['provider_id'] = orig_tx.provider_id.id
        res['original_invoice_id'] = credit_note.reversed_entry_id.id
        res['original_poynt_txn_id'] = orig_tx.poynt_transaction_id

        provider = orig_tx.provider_id.sudo()
        if provider.poynt_default_terminal_id:
            res['terminal_id'] = provider.poynt_default_terminal_id.id

        age_days = 0
        if orig_tx.create_date:
            age_days = (fields.Datetime.now() - orig_tx.create_date).days
        res['transaction_age_days'] = age_days

        if age_days > REFERENCED_REFUND_LIMIT_DAYS:
            res['refund_type'] = 'non_referenced'
            res['refund_type_note'] = _(
                "This transaction is %(days)s days old (limit is %(limit)s "
                "days). A non-referenced credit will be issued. This "
                "requires the customer's card to be present on the terminal.",
                days=age_days,
                limit=REFERENCED_REFUND_LIMIT_DAYS,
            )
        else:
            res['refund_type'] = 'referenced'
            res['refund_type_note'] = _(
                "This transaction is %(days)s days old (within the %(limit)s-day "
                "limit). A referenced refund will be issued back to the "
                "original card automatically.",
                days=age_days,
                limit=REFERENCED_REFUND_LIMIT_DAYS,
            )

        receipt_data = orig_tx.poynt_receipt_data
        if receipt_data:
            try:
                data = json.loads(receipt_data)
                card_type = data.get('card_type', '')
                card_last4 = data.get('card_last4', '')
                if card_type or card_last4:
                    res['card_info'] = f"{card_type} ****{card_last4}"
            except (ValueError, KeyError):
                pass

        return res

    def action_process_refund(self):
        """Dispatch to referenced refund or non-referenced credit."""
        self.ensure_one()

        if self.amount <= 0:
            raise UserError(_("Refund amount must be greater than zero."))

        orig_tx = self.original_transaction_id
        if orig_tx.poynt_voided:
            raise UserError(_(
                "This transaction was already voided on Poynt on %(date)s. "
                "A voided transaction cannot also be refunded -- the charge "
                "was already reversed before settlement.",
                date=orig_tx.poynt_void_date,
            ))

        self._verify_transaction_not_already_reversed()

        if self.refund_type == 'non_referenced':
            return self._process_non_referenced_credit()
        return self._process_referenced_refund()

    def _verify_transaction_not_already_reversed(self):
        """Check on Poynt that the transaction and all linked children
        have not been voided or refunded.

        For SALE transactions Poynt creates AUTHORIZE + CAPTURE children.
        A void/refund may target the capture child, leaving the parent
        still showing ``status: CAPTURED``.  We must check the full chain.
        """
        orig_tx = self.original_transaction_id
        provider = self._get_provider_sudo()
        txn_id = orig_tx.poynt_transaction_id

        try:
            txn_data = provider._poynt_make_request(
                'GET', f'transactions/{txn_id}',
            )
        except (ValidationError, Exception):
            _logger.debug("Could not verify transaction %s on Poynt", txn_id)
            return

        self._check_txn_reversed(txn_data, orig_tx)

        for link in txn_data.get('links', []):
            child_id = link.get('href', '')
            if not child_id:
                continue
            try:
                child_data = provider._poynt_make_request(
                    'GET', f'transactions/{child_id}',
                )
                self._check_txn_reversed(child_data, orig_tx)
            except (ValidationError, Exception):
                continue

    def _check_txn_reversed(self, txn_data, orig_tx):
        """Raise if the given Poynt transaction has been voided or refunded."""
        txn_id = txn_data.get('id', '?')

        if txn_data.get('voided'):
            _logger.warning(
                "Poynt txn %s is voided, blocking refund", txn_id,
            )
            if not orig_tx.poynt_voided:
                orig_tx.sudo().write({
                    'state': 'cancel',
                    'poynt_voided': True,
                    'poynt_void_date': fields.Datetime.now(),
                })
            raise UserError(_(
                "This transaction (%(txn_id)s) has already been voided on "
                "Poynt. The charge was reversed before settlement -- no "
                "refund is needed.",
                txn_id=txn_id,
            ))

        status = txn_data.get('status', '')
        if status == 'REFUNDED':
            _logger.warning(
                "Poynt txn %s is already refunded, blocking duplicate", txn_id,
            )
            raise UserError(_(
                "This transaction (%(txn_id)s) has already been refunded on "
                "Poynt. A duplicate refund cannot be issued.",
                txn_id=txn_id,
            ))

        if status == 'VOIDED':
            _logger.warning(
                "Poynt txn %s has VOIDED status, blocking refund", txn_id,
            )
            if not orig_tx.poynt_voided:
                orig_tx.sudo().write({
                    'state': 'cancel',
                    'poynt_voided': True,
                    'poynt_void_date': fields.Datetime.now(),
                })
            raise UserError(_(
                "This transaction (%(txn_id)s) has already been voided on "
                "Poynt. The charge was reversed before settlement -- no "
                "refund is needed.",
                txn_id=txn_id,
            ))

    def _process_referenced_refund(self):
        """Send a referenced REFUND using the original transaction's parentId."""
        orig_tx = self.original_transaction_id
        provider = self._get_provider_sudo()

        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']
                    _logger.info(
                        "Refund: using captureId %s instead of original %s",
                        parent_txn_id, orig_tx.poynt_transaction_id,
                    )
                    break
        except (ValidationError, Exception):
            _logger.debug(
                "Could not fetch parent txn %s, using original ID",
                parent_txn_id,
            )

        minor_amount = poynt_utils.format_poynt_amount(
            self.amount, self.currency_id,
        )

        refund_payload = {
            'action': 'REFUND',
            'parentId': parent_txn_id,
            'fundingSource': {
                'type': 'CREDIT_DEBIT',
            },
            'amounts': {
                'transactionAmount': minor_amount,
                'orderAmount': minor_amount,
                'currency': self.currency_id.name,
            },
            'context': {
                'source': 'WEB',
                'sourceApp': 'odoo.fusion_poynt',
            },
            'notes': f'Refund for {orig_tx.reference} via {self.credit_note_id.name}',
        }

        try:
            result = provider._poynt_make_request(
                'POST', 'transactions', payload=refund_payload,
            )
        except (ValidationError, UserError) as e:
            self.write({
                'state': 'error',
                'status_message': str(e),
            })
            return self._reopen_wizard()

        return self._handle_refund_result(result, orig_tx)

    def _process_non_referenced_credit(self):
        """Send a non-referenced credit via cloud message to the terminal.

        Required when the original transaction is older than 180 days.
        The customer's card must be present on the terminal.
        """
        if not self.terminal_id:
            raise UserError(_(
                "A terminal is required for non-referenced credits. "
                "The customer's card must be present on the device."
            ))

        provider = self._get_provider_sudo()
        orig_tx = self.original_transaction_id
        minor_amount = poynt_utils.format_poynt_amount(
            self.amount, self.currency_id,
        )

        reference = f"NRC-{self.credit_note_id.name}"

        payment_data = json.dumps({
            'amount': minor_amount,
            'currency': self.currency_id.name,
            'nonReferencedCredit': True,
            'referenceId': reference,
            'callbackUrl': '',
        })

        cloud_payload = {
            'ttl': 120,
            'businessId': provider.poynt_business_id,
            'storeId': provider.poynt_store_id,
            'deviceId': self.terminal_id.terminal_id,
            'data': json.dumps({
                'action': 'non-referenced-credit',
                'purchaseAmount': minor_amount,
                'currency': self.currency_id.name,
                'referenceId': reference,
                'payment': payment_data,
            }),
        }

        try:
            provider._poynt_make_request(
                'POST', 'cloudMessages',
                payload=cloud_payload,
                business_scoped=False,
            )
        except (ValidationError, UserError) as e:
            self.write({
                'state': 'error',
                'status_message': str(e),
            })
            return self._reopen_wizard()

        refund_tx = self._create_refund_transaction(
            orig_tx,
            refund_txn_id='',
            refund_status='PENDING',
        )

        self.credit_note_id.sudo().poynt_refunded = True

        self.credit_note_id.sudo().message_post(
            body=_(
                "Non-referenced credit sent to terminal '%(terminal)s'. "
                "Amount: %(amount)s %(currency)s. "
                "The customer must present their card on the terminal to "
                "complete the refund.",
                terminal=self.terminal_id.name,
                amount=self.amount,
                currency=self.currency_id.name,
            ),
        )

        self.write({
            'state': 'done',
            'status_message': _(
                "Non-referenced credit of %(amount)s %(currency)s sent to "
                "terminal '%(terminal)s'. Please ask the customer to present "
                "their card on the terminal to complete the refund.",
                amount=self.amount,
                currency=self.currency_id.name,
                terminal=self.terminal_id.name,
            ),
        })

        return self._reopen_wizard()

    def _handle_refund_result(self, result, orig_tx):
        """Process the Poynt API response for a referenced refund."""
        refund_status = result.get('status', '')
        refund_txn_id = result.get('id', '')

        _logger.info(
            "Poynt refund response: status=%s, id=%s",
            refund_status, refund_txn_id,
        )

        if refund_status in ('DECLINED', 'FAILED'):
            self.write({
                'state': 'error',
                'status_message': _(
                    "Refund declined by the payment processor. "
                    "Status: %(status)s. Please try again or contact support.",
                    status=refund_status,
                ),
            })
            return self._reopen_wizard()

        refund_tx = self._create_refund_transaction(orig_tx, refund_txn_id, refund_status)
        self.refund_transaction_id = refund_tx

        self.credit_note_id.sudo().poynt_refunded = True

        self.credit_note_id.sudo().message_post(
            body=_(
                "Refund processed via Poynt. Amount: %(amount)s %(currency)s. "
                "Poynt Transaction ID: %(txn_id)s.",
                amount=self.amount,
                currency=self.currency_id.name,
                txn_id=refund_txn_id,
            ),
        )

        self.write({
            'state': 'done',
            'status_message': _(
                "Refund of %(amount)s %(currency)s processed successfully. "
                "The refund will appear on the customer's card within "
                "3-5 business days.",
                amount=self.amount,
                currency=self.currency_id.name,
            ),
        })

        return self._reopen_wizard()

    def _create_refund_transaction(self, orig_tx, refund_txn_id, refund_status):
        """Create a payment.transaction for the refund."""
        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': self.provider_id.id,
            'payment_method_id': payment_method.id if payment_method else False,
            'amount': -self.amount,
            'currency_id': self.currency_id.id,
            'partner_id': self.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, self.credit_note_id.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)

        return refund_tx

    def action_send_receipt(self):
        """Email the refund receipt to the customer and close the wizard."""
        self.ensure_one()
        tx = self.refund_transaction_id
        if not tx:
            raise UserError(_("No refund transaction found."))

        template = self.env.ref(
            'fusion_poynt.mail_template_poynt_receipt', raise_if_not_found=False,
        )
        if not template:
            raise UserError(_("Receipt email template not found."))

        template.send_mail(tx.id, force_send=True)

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

    def _reopen_wizard(self):
        return {
            'type': 'ir.actions.act_window',
            'name': _("Refund via Poynt"),
            'res_model': self._name,
            'res_id': self.id,
            'views': [(False, 'form')],
            'target': 'new',
        }
