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

import logging

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__)


class PoyntPaymentWizard(models.TransientModel):
    _name = 'poynt.payment.wizard'
    _description = 'Collect Poynt Payment'

    invoice_id = fields.Many2one(
        'account.move',
        string="Invoice",
        required=True,
        readonly=True,
        domain="[('move_type', 'in', ('out_invoice', 'out_refund'))]",
    )
    partner_id = fields.Many2one(
        related='invoice_id.partner_id',
        string="Customer",
    )
    amount = fields.Monetary(
        string="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,
        domain="[('code', '=', 'poynt'), ('state', '!=', 'disabled')]",
    )
    provider_name = fields.Char(
        related='provider_id.name',
        string="Poynt Provider",
        readonly=True,
    )

    payment_mode = fields.Selection(
        selection=[
            ('terminal', "Send to Terminal"),
            ('card', "Manual Card Entry"),
        ],
        string="Payment Mode",
        required=True,
        default='terminal',
    )

    # --- Terminal fields ---
    terminal_id = fields.Many2one(
        'poynt.terminal',
        string="Terminal",
        domain="[('provider_id', '=', provider_id), ('active', '=', True)]",
    )

    # --- Card entry fields (never stored, transient only) ---
    card_number = fields.Char(string="Card Number")
    exp_month = fields.Char(string="Exp. Month", size=2)
    exp_year = fields.Char(string="Exp. Year", size=4)
    cvv = fields.Char(string="CVV", size=4)
    cardholder_name = fields.Char(string="Cardholder Name")

    # --- Status tracking for terminal mode ---
    state = fields.Selection(
        selection=[
            ('draft', "Draft"),
            ('waiting', "Waiting for Terminal"),
            ('done', "Payment Collected"),
            ('error', "Error"),
        ],
        default='draft',
    )
    status_message = fields.Text(string="Status", readonly=True)
    poynt_transaction_ref = fields.Char(readonly=True)
    poynt_order_id = fields.Char(
        string="Poynt Order ID",
        readonly=True,
        help="The Poynt order UUID created for this payment attempt. "
             "Used to verify the correct terminal transaction.",
    )
    sent_at = fields.Datetime(
        string="Sent At",
        readonly=True,
        help="Timestamp when the payment was sent to the terminal. "
             "Used to filter out older transactions during status checks.",
    )
    transaction_id = fields.Many2one(
        'payment.transaction',
        string="Payment Transaction",
        readonly=True,
    )

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

        if active_model == 'account.move' and invoice_id:
            invoice = self.env['account.move'].browse(invoice_id)
            res['invoice_id'] = invoice.id
            res['amount'] = invoice.amount_residual
            res['currency_id'] = invoice.currency_id.id

        provider = self.env['payment.provider'].sudo().search([
            ('code', '=', 'poynt'),
            ('state', '!=', 'disabled'),
        ], limit=1)
        if provider:
            res['provider_id'] = provider.id
            if provider.poynt_default_terminal_id:
                res['terminal_id'] = provider.poynt_default_terminal_id.id

        return res

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

    @api.onchange('provider_id')
    def _onchange_provider_id(self):
        if self.provider_id:
            provider = self._get_provider_sudo()
            if provider.poynt_default_terminal_id:
                self.terminal_id = provider.poynt_default_terminal_id

    def action_collect_payment(self):
        """Dispatch to the appropriate payment method."""
        self.ensure_one()

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

        self._cleanup_draft_transaction()

        if self.payment_mode == 'terminal':
            return self._process_terminal_payment()
        elif self.payment_mode == 'card':
            return self._process_card_payment()

    def _process_terminal_payment(self):
        """Send a payment request to the physical Poynt terminal."""
        self.ensure_one()

        if not self.terminal_id:
            raise UserError(_("Please select a terminal."))

        tx = self._create_payment_transaction()
        reference = tx.reference

        try:
            order_data = self._create_poynt_order(reference)
            order_id = order_data.get('id', '')
            tx.poynt_order_id = order_id

            self.terminal_id.action_send_payment_to_terminal(
                amount=self.amount,
                currency=self.currency_id,
                reference=reference,
                order_id=order_id,
            )

            self.write({
                'state': 'waiting',
                'status_message': _(
                    "Payment request sent to terminal '%(terminal)s'. "
                    "Please complete the transaction on the device.",
                    terminal=self.terminal_id.name,
                ),
                'poynt_transaction_ref': reference,
                'poynt_order_id': order_id,
                'sent_at': fields.Datetime.now(),
            })

            return self._open_poll_action()

        except (ValidationError, UserError) as e:
            self._cleanup_draft_transaction()
            self.write({
                'state': 'error',
                'status_message': str(e),
            })
            return self._reopen_wizard()

    def _process_card_payment(self):
        """Process a manual card entry payment via Poynt Cloud API."""
        self.ensure_one()
        self._validate_card_fields()

        tx = self._create_payment_transaction()
        reference = tx.reference

        try:
            order_data = self._create_poynt_order(reference)
            order_id = order_data.get('id', '')
            tx.poynt_order_id = order_id

            funding_source = {
                'type': 'CREDIT_DEBIT',
                'card': {
                    'number': self.card_number.replace(' ', ''),
                    'expirationMonth': int(self.exp_month),
                    'expirationYear': int(self.exp_year),
                    'cardHolderFullName': self.cardholder_name or '',
                },
                'verificationData': {
                    'cvData': self.cvv,
                },
                'entryDetails': {
                    'customerPresenceStatus': 'MOTO',
                    'entryMode': 'KEYED',
                },
            }

            provider = self._get_provider_sudo()
            action = 'AUTHORIZE' if provider.capture_manually else 'SALE'
            minor_amount = poynt_utils.format_poynt_amount(
                self.amount, self.currency_id,
            )

            txn_payload = {
                'action': action,
                'amounts': {
                    'transactionAmount': minor_amount,
                    'orderAmount': minor_amount,
                    'tipAmount': 0,
                    'cashbackAmount': 0,
                    'currency': self.currency_id.name,
                },
                'fundingSource': funding_source,
                'context': {
                    'source': 'WEB',
                    'sourceApp': 'odoo.fusion_poynt',
                    'transactionInstruction': 'ONLINE_AUTH_REQUIRED',
                    'businessId': provider.poynt_business_id,
                },
                'notes': reference,
            }

            if order_id:
                txn_payload['references'] = [{
                    'id': order_id,
                    'type': 'POYNT_ORDER',
                }]

            result = provider._poynt_make_request(
                'POST', 'transactions', payload=txn_payload,
            )

            transaction_id = result.get('id', '')
            status = result.get('status', '')

            tx.write({
                'poynt_transaction_id': transaction_id,
                'provider_reference': transaction_id,
            })

            payment_data = {
                'reference': reference,
                'poynt_transaction_id': transaction_id,
                'poynt_order_id': order_id,
                'poynt_status': status,
                'funding_source': result.get('fundingSource', {}),
            }
            tx._process('poynt', payment_data)

            self.write({
                'state': 'done',
                'status_message': _(
                    "Payment collected successfully. Transaction: %(txn_id)s",
                    txn_id=transaction_id,
                ),
                'poynt_transaction_ref': transaction_id,
            })

            return self._reopen_wizard()

        except (ValidationError, UserError) as e:
            self._cleanup_draft_transaction()
            self.write({
                'state': 'error',
                'status_message': str(e),
            })
            return self._reopen_wizard()

    def action_check_status(self):
        """Poll the terminal for payment status (used in waiting state).

        Uses the Poynt order ID to look up transactions associated with
        the specific order we created for this payment attempt.  Falls
        back to referenceId search with time filtering to avoid matching
        transactions from previous attempts.

        Returns False if still pending (JS poller keeps going), or an
        act_window action to show the final result.
        """
        self.ensure_one()

        if not self.poynt_transaction_ref:
            raise UserError(_("No payment reference to check."))

        terminal = self.terminal_id
        if not terminal:
            raise UserError(_("No terminal associated with this payment."))

        provider = self._get_provider_sudo()

        try:
            txn = self._find_terminal_transaction(provider)
        except (ValidationError, UserError):
            return False

        if not txn:
            return False

        status = txn.get('status', 'UNKNOWN')
        transaction_id = txn.get('id', '')

        if status in ('CAPTURED', 'AUTHORIZED', 'SETTLED'):
            tx = self.transaction_id
            if tx:
                tx.write({
                    'poynt_transaction_id': transaction_id,
                    'provider_reference': transaction_id,
                })
                payment_data = {
                    'reference': self.poynt_transaction_ref,
                    'poynt_transaction_id': transaction_id,
                    'poynt_status': status,
                    'funding_source': txn.get('fundingSource', {}),
                }
                tx._process('poynt', payment_data)

            self.write({
                'state': 'done',
                'status_message': _(
                    "Payment collected successfully on terminal."
                ),
            })
            return self._reopen_wizard()

        if status in ('DECLINED', 'VOIDED', 'REFUNDED'):
            self._cleanup_draft_transaction()
            self.write({
                'state': 'error',
                'status_message': _(
                    "Payment was %(status)s on the terminal.",
                    status=status.lower(),
                ),
            })
            return self._reopen_wizard()

        return False

    def _find_terminal_transaction(self, provider):
        """Locate the Poynt transaction for the current payment attempt.

        Strategy:
          1. If we have a poynt_order_id, fetch the order from Poynt and
             check its linked transactions for a completed payment.
          2. Fall back to searching transactions by referenceId, but only
             accept transactions created after we sent the cloud message
             (self.sent_at) to avoid matching older transactions.

        :return: The matching Poynt transaction dict, or None if pending.
        :rtype: dict | None
        """
        if self.poynt_order_id:
            txn = self._check_order_transactions(provider)
            if txn:
                return txn

        return self._check_by_reference_with_time_filter(provider)

    def _check_order_transactions(self, provider):
        """Look up transactions linked to our Poynt order."""
        try:
            order_data = provider._poynt_make_request(
                'GET', f'orders/{self.poynt_order_id}',
            )
        except (ValidationError, UserError):
            _logger.debug("Could not fetch Poynt order %s", self.poynt_order_id)
            return None

        txn_ids = []
        for item in order_data.get('transactions', []):
            tid = item if isinstance(item, str) else item.get('id', '')
            if tid:
                txn_ids.append(tid)

        for tid in txn_ids:
            try:
                txn_data = provider._poynt_make_request(
                    'GET', f'transactions/{tid}',
                )
                status = txn_data.get('status', '')
                if status in ('CAPTURED', 'AUTHORIZED', 'SETTLED',
                              'DECLINED', 'VOIDED', 'REFUNDED'):
                    return txn_data
            except (ValidationError, UserError):
                continue

        return None

    def _check_by_reference_with_time_filter(self, provider):
        """Search transactions by referenceId, filtering by creation time."""
        sent_at_str = ''
        if self.sent_at:
            sent_at_str = self.sent_at.strftime('%Y-%m-%dT%H:%M:%SZ')

        params = {
            'referenceId': self.poynt_transaction_ref,
            'limit': 5,
        }
        if sent_at_str:
            params['startAt'] = sent_at_str

        try:
            txn_result = provider._poynt_make_request(
                'GET', 'transactions', params=params,
            )
        except (ValidationError, UserError):
            return None

        transactions = txn_result.get('transactions', [])
        if not transactions and not sent_at_str:
            try:
                txn_result = provider._poynt_make_request(
                    'GET', 'transactions',
                    params={'notes': self.poynt_transaction_ref, 'limit': 5},
                )
                transactions = txn_result.get('transactions', [])
            except (ValidationError, UserError):
                return None

        for txn in transactions:
            status = txn.get('status', 'UNKNOWN')
            if status in ('CAPTURED', 'AUTHORIZED', 'SETTLED',
                          'DECLINED', 'VOIDED', 'REFUNDED'):
                return txn

        return None

    def action_send_receipt(self):
        """Email the payment receipt to the customer and close the wizard."""
        self.ensure_one()
        tx = self.transaction_id
        if not tx:
            raise UserError(_("No payment 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 action_cancel_payment(self):
        """Cancel the payment and clean up the draft transaction."""
        self.ensure_one()
        self._cleanup_draft_transaction()
        return {'type': 'ir.actions.act_window_close'}

    def _cleanup_draft_transaction(self):
        """Remove the draft payment transaction created by this wizard."""
        if not self.transaction_id:
            return
        tx = self.transaction_id.sudo()
        if tx.state == 'draft':
            tx.invoice_ids = [(5,)]
            tx.unlink()
            self.transaction_id = False

    # === HELPERS === #

    def _validate_card_fields(self):
        """Validate that card entry fields are properly filled."""
        if not self.card_number or len(self.card_number.replace(' ', '')) < 13:
            raise UserError(_("Please enter a valid card number."))
        if not self.exp_month or not self.exp_month.isdigit():
            raise UserError(_("Please enter a valid expiry month (01-12)."))
        if not self.exp_year or not self.exp_year.isdigit() or len(self.exp_year) < 2:
            raise UserError(_("Please enter a valid expiry year."))
        if not self.cvv or not self.cvv.isdigit():
            raise UserError(_("Please enter the CVV."))

    def _create_payment_transaction(self):
        """Create a payment.transaction linked to the invoice."""
        PaymentMethod = self.env['payment.method'].sudo().with_context(active_test=False)
        payment_method = PaymentMethod.search(
            [('code', '=', 'card')], limit=1,
        )
        if not payment_method:
            payment_method = PaymentMethod.search(
                [('code', 'in', ('visa', 'mastercard'))], limit=1,
            )
        if not payment_method:
            raise UserError(
                _("No card payment method found. Please configure one "
                  "in Settings > Payment Methods.")
            )

        tx_values = {
            'provider_id': self.provider_id.id,
            'payment_method_id': payment_method.id,
            'amount': self.amount,
            'currency_id': self.currency_id.id,
            'partner_id': self.partner_id.id,
            'operation': 'offline',
            'invoice_ids': [(4, self.invoice_id.id)],
        }
        tx = self.env['payment.transaction'].sudo().create(tx_values)
        self.transaction_id = tx
        return tx

    def _create_poynt_order(self, reference):
        """Create a Poynt order via the API."""
        provider = self._get_provider_sudo()
        order_payload = poynt_utils.build_order_payload(
            reference,
            self.amount,
            self.currency_id,
            business_id=provider.poynt_business_id,
            store_id=provider.poynt_store_id or '',
        )
        return provider._poynt_make_request(
            'POST', 'orders', payload=order_payload,
        )

    def _reopen_wizard(self):
        """Return an action that re-opens this wizard record (keeps state)."""
        return {
            'type': 'ir.actions.act_window',
            'name': _("Collect Poynt Payment"),
            'res_model': self._name,
            'res_id': self.id,
            'views': [(False, 'form')],
            'target': 'new',
        }

    def _open_poll_action(self):
        """Return a client action that auto-polls the terminal status."""
        return {
            'type': 'ir.actions.client',
            'tag': 'poynt_poll_action',
            'name': _("Waiting for Terminal"),
            'target': 'new',
            'params': {
                'wizard_id': self.id,
            },
        }
