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

import json
import logging
import pprint

from odoo import fields, http
from odoo.exceptions import UserError, ValidationError
from odoo.http import request
from odoo.tools import mute_logger

from odoo.addons.fusion_clover import utils as clover_utils

_logger = logging.getLogger(__name__)


def _detect_card_brand(card_number):
    """Detect the card brand from the card number using BIN prefixes."""
    num = (card_number or '').replace(' ', '')
    if len(num) < 2:
        return 'other'
    if num[:2] in ('34', '37'):
        return 'amex'
    if num[0] == '4':
        return 'visa'
    prefix2 = int(num[:2])
    if 51 <= prefix2 <= 55:
        return 'mastercard'
    if len(num) >= 4:
        prefix4 = int(num[:4])
        if 2221 <= prefix4 <= 2720:
            return 'mastercard'
    return 'other'


class CloverController(http.Controller):
    _return_url = '/payment/clover/return'
    _webhook_url = '/payment/clover/webhook'
    _oauth_callback_url = '/payment/clover/oauth/callback'

    # === RETURN ROUTE === #

    @http.route(_return_url, type='http', methods=['GET'], auth='public')
    def clover_return(self, **data):
        """Process the return from a Clover payment flow."""
        tx_sudo = request.env['payment.transaction'].sudo()._search_by_reference(
            'clover', data,
        )

        if tx_sudo and tx_sudo.clover_charge_id:
            try:
                provider = tx_sudo.provider_id.sudo()
                charge_data = provider._clover_make_ecom_request(
                    'GET', f'v1/charges/{tx_sudo.clover_charge_id}',
                )
                payment_data = {
                    'reference': tx_sudo.reference,
                    'clover_charge_id': charge_data.get('id'),
                    'clover_status': charge_data.get('status', ''),
                    'source': charge_data.get('source', {}),
                }
                tx_sudo._process('clover', payment_data)
            except ValidationError:
                _logger.error(
                    "Failed to fetch Clover charge %s on return.",
                    tx_sudo.clover_charge_id,
                )

        with mute_logger('werkzeug'):
            return request.redirect('/payment/status')

    # === WEBHOOK ROUTE === #

    @http.route(_webhook_url, type='http', methods=['POST'], auth='public', csrf=False)
    def clover_webhook(self):
        """Process webhook notifications from Clover."""
        try:
            raw_body = request.httprequest.data.decode('utf-8')
            event = json.loads(raw_body)
        except (ValueError, UnicodeDecodeError):
            _logger.warning("Received invalid JSON from Clover webhook")
            return request.make_json_response({'status': 'error'}, status=400)

        _logger.info(
            "Clover webhook notification received:\n%s",
            pprint.pformat(event),
        )

        try:
            event_type = event.get('type', '')
            data = event.get('data', {})

            if event_type in ('charge.succeeded', 'charge.captured'):
                self._handle_charge_webhook(data, 'succeeded')
            elif event_type == 'charge.failed':
                self._handle_charge_webhook(data, 'failed')
            elif event_type == 'charge.voided':
                self._handle_void_webhook(data)
            elif event_type in ('refund.created', 'refund.succeeded'):
                self._handle_refund_webhook(data)
            elif event_type == 'refund.failed':
                _logger.warning("Clover refund failed webhook: %s", data.get('id', ''))
            elif event_type == 'payment.created':
                self._handle_charge_webhook(data, 'succeeded')

        except ValidationError:
            _logger.exception("Unable to process Clover webhook; acknowledging to avoid retries")

        return request.make_json_response({'status': 'ok'})

    def _handle_charge_webhook(self, data, status):
        """Process a charge-related webhook event."""
        charge_id = data.get('id', '')
        if not charge_id:
            return

        payment_data = {
            'clover_charge_id': charge_id,
            'clover_status': status,
            'source': data.get('source', {}),
        }

        # Try to find by metadata reference first
        metadata = data.get('metadata', {})
        reference = metadata.get('odoo_reference', '')
        if reference:
            payment_data['reference'] = reference

        tx_sudo = request.env['payment.transaction'].sudo()._search_by_reference(
            'clover', payment_data,
        )

        if tx_sudo:
            tx_sudo._process('clover', payment_data)

    def _handle_void_webhook(self, data):
        """Process a void webhook event."""
        charge_id = data.get('id', '')
        if not charge_id:
            return

        tx_sudo = request.env['payment.transaction'].sudo().search([
            ('clover_charge_id', '=', charge_id),
            ('provider_code', '=', 'clover'),
            ('state', '=', 'done'),
        ], limit=1)

        if not tx_sudo:
            return

        if not tx_sudo.clover_voided:
            tx_sudo.sudo().write({
                'state': 'cancel',
                'clover_voided': True,
                'clover_void_date': fields.Datetime.now(),
            })
            _logger.info("Clover void webhook: voided transaction %s", tx_sudo.reference)

    def _handle_refund_webhook(self, data):
        """Process a refund webhook event."""
        refund_id = data.get('id', '')
        charge_id = data.get('charge', '')
        if not charge_id:
            return

        source_tx = request.env['payment.transaction'].sudo().search([
            ('clover_charge_id', '=', charge_id),
            ('provider_code', '=', 'clover'),
            ('state', '=', 'done'),
        ], limit=1)

        if not source_tx:
            return

        existing_refund = source_tx.child_transaction_ids.filtered(
            lambda t: t.provider_reference == refund_id
        )
        if existing_refund:
            return

        refund_amount_minor = data.get('amount', 0)
        refund_amount = clover_utils.parse_clover_amount(
            refund_amount_minor, source_tx.currency_id,
        )

        refund_tx = source_tx._create_child_transaction(
            refund_amount, is_refund=True,
        )
        payment_data = {
            'reference': refund_tx.reference,
            'clover_charge_id': charge_id,
            'clover_refund_id': refund_id,
            'clover_status': 'succeeded',
        }
        refund_tx._process('clover', payment_data)

    # === OAUTH CALLBACK ROUTE === #

    @http.route(_oauth_callback_url, type='http', methods=['GET'], auth='user')
    def clover_oauth_callback(self, **data):
        """Handle the OAuth2 authorization callback from Clover.

        After a merchant authorizes the app, Clover redirects here with
        an authorization code. We exchange it for an access token and
        store the merchant_id.
        """
        code = data.get('code', '')
        merchant_id = data.get('merchant_id', '')
        client_id = data.get('client_id', '')
        state = data.get('state', '')

        if not code:
            _logger.warning("Clover OAuth callback missing authorization code")
            return request.redirect('/odoo/settings')

        if state:
            try:
                provider_id = int(state)
                provider = request.env['payment.provider'].browse(provider_id)
                if provider.exists() and provider.code == 'clover':
                    # Exchange code for access token
                    import requests as req
                    is_test = provider.state == 'test'
                    token_url = (
                        f"{data.get('_token_url', '')}"
                        or (
                            'https://apisandbox.dev.clover.com/oauth/token' if is_test
                            else 'https://api.clover.com/oauth/token'
                        )
                    )
                    token_resp = req.get(token_url, params={
                        'client_id': provider.clover_app_id,
                        'client_secret': provider.sudo().clover_app_secret,
                        'code': code,
                    }, timeout=30)

                    if token_resp.status_code == 200:
                        token_data = token_resp.json()
                        access_token = token_data.get('access_token', '')
                        if access_token:
                            vals = {'clover_api_key': access_token}
                            if merchant_id:
                                vals['clover_merchant_id'] = merchant_id
                            provider.sudo().write(vals)
                            _logger.info(
                                "Clover OAuth: linked merchant %s to provider %s",
                                merchant_id, provider_id,
                            )
                    else:
                        _logger.error(
                            "Clover OAuth token exchange failed: %s",
                            token_resp.text[:500],
                        )
            except (ValueError, TypeError):
                _logger.warning("Invalid provider state in Clover OAuth callback: %s", state)

        return request.redirect('/odoo/settings')

    # === SURCHARGE HELPER === #

    def _apply_portal_surcharge(self, tx_sudo, card_type):
        """Apply credit card surcharge to the linked invoice if enabled."""
        ICP = request.env['ir.config_parameter'].sudo()
        if ICP.get_param('fusion_clover.surcharge_enabled', 'False') != 'True':
            return 0.0

        if not card_type:
            card_type = 'other'

        rate_key = {
            'visa': 'fusion_clover.surcharge_visa_rate',
            'mastercard': 'fusion_clover.surcharge_mastercard_rate',
            'amex': 'fusion_clover.surcharge_amex_rate',
            'debit': 'fusion_clover.surcharge_debit_rate',
        }.get(card_type, 'fusion_clover.surcharge_other_rate')

        rate = float(ICP.get_param(rate_key, '0') or 0)
        if rate <= 0:
            return 0.0

        invoices = tx_sudo.invoice_ids
        if not invoices:
            base_amount = tx_sudo.amount
        else:
            base_amount = sum(invoices.mapped('amount_residual'))

        fee_amount = round(base_amount * rate / 100.0, 2)
        if fee_amount <= 0:
            return 0.0

        product_id = int(ICP.get_param('fusion_clover.surcharge_product_id', '0') or 0)
        product = request.env['product.product'].sudo().browse(product_id).exists()
        if not product:
            product = request.env.ref(
                'fusion_clover.product_cc_processing_fee', raise_if_not_found=False,
            )
        if not product:
            _logger.warning("Surcharge product not configured; skipping surcharge")
            return 0.0

        for invoice in invoices.sudo():
            was_posted = invoice.state == 'posted'
            if was_posted:
                invoice.button_draft()

            description = "Credit Card Processing Fee (%.2f%% surcharge)" % rate
            invoice.write({
                'invoice_line_ids': [(0, 0, {
                    'product_id': product.id,
                    'name': description,
                    'quantity': 1,
                    'price_unit': fee_amount,
                    'tax_ids': [(5, 0, 0)],
                })],
            })

            if was_posted:
                invoice.action_post()

        new_amount = tx_sudo.amount + fee_amount
        tx_sudo.write({'amount': new_amount})
        return fee_amount

    # === TERMINAL ROUTES === #

    @http.route('/payment/clover/terminals', type='jsonrpc', auth='public')
    def clover_list_terminals(self, provider_id=None, **kwargs):
        """Return a list of active terminals for the given Clover provider."""
        if not provider_id:
            return []

        terminals = request.env['clover.terminal'].sudo().search([
            ('provider_id', '=', int(provider_id)),
            ('active', '=', True),
        ])

        return [
            {
                'id': t.id,
                'name': t.name,
                'serial': t.serial_number,
                'status': t.status or 'unknown',
                'model': t.model_name or '',
            }
            for t in terminals
        ]

    @http.route('/payment/clover/send_to_terminal', type='jsonrpc', auth='public')
    def clover_send_to_terminal(self, reference=None, terminal_id=None,
                                card_type=None, **kwargs):
        """Send a payment request to a Clover terminal via Cloud Pay Display."""
        if not reference or not terminal_id:
            return {'error': 'Missing reference or terminal ID.'}

        tx_sudo = request.env['payment.transaction'].sudo().search([
            ('reference', '=', reference),
            ('provider_code', '=', 'clover'),
        ], limit=1)
        if not tx_sudo:
            return {'error': 'Transaction not found.'}

        terminal = request.env['clover.terminal'].sudo().browse(int(terminal_id))
        if not terminal.exists():
            return {'error': 'Terminal not found.'}

        try:
            if card_type:
                self._apply_portal_surcharge(tx_sudo, card_type)

            provider = tx_sudo.provider_id.sudo()
            capture = not provider.capture_manually

            result = terminal.action_send_payment(
                amount=tx_sudo.amount,
                currency=tx_sudo.currency_id,
                reference=reference,
                capture=capture,
            )

            return {'success': True}
        except (ValidationError, UserError) as e:
            return {'error': str(e)}

    @http.route('/payment/clover/terminal_status', type='jsonrpc', auth='public')
    def clover_terminal_status(self, reference=None, terminal_id=None, **kwargs):
        """Poll for the status of a terminal payment."""
        if not reference or not terminal_id:
            return {'status': 'error', 'message': 'Missing reference or terminal ID.'}

        terminal = request.env['clover.terminal'].sudo().browse(int(terminal_id))
        if not terminal.exists():
            return {'status': 'error', 'message': 'Terminal not found.'}

        result = terminal.action_check_payment_status(reference)
        status = result.get('status', 'pending')

        # If payment completed, process the transaction
        if status in ('CLOSED', 'AUTH', 'AUTHORIZED', 'CAPTURED'):
            tx_sudo = request.env['payment.transaction'].sudo().search([
                ('reference', '=', reference),
                ('provider_code', '=', 'clover'),
            ], limit=1)

            if tx_sudo and tx_sudo.state == 'draft':
                payment_id = result.get('payment_id', '')
                card_txn = result.get('card_transaction', {})

                tx_sudo.write({
                    'clover_charge_id': payment_id or tx_sudo.clover_charge_id,
                    'provider_reference': payment_id or tx_sudo.provider_reference,
                })
                payment_data = {
                    'reference': reference,
                    'clover_charge_id': payment_id,
                    'clover_status': 'succeeded',
                    'source': {
                        'brand': card_txn.get('cardType', ''),
                        'last4': card_txn.get('last4', ''),
                    },
                }
                tx_sudo._process('clover', payment_data)

        return result

    @http.route('/payment/clover/terminal/callback', type='http',
                methods=['POST'], auth='public', csrf=False)
    def clover_terminal_callback(self, **data):
        """Handle callback from terminal payment completion (if configured)."""
        _logger.info("Clover terminal callback received: %s", data)
        return request.make_json_response({'status': 'ok'})

    # === JSON-RPC ROUTES (called from frontend JS) === #

    @http.route('/payment/clover/process_card', type='jsonrpc', auth='public')
    def clover_process_card(self, reference=None, card_token=None,
                            card_type=None, **kwargs):
        """Process a card payment through Clover Ecommerce API.

        The frontend tokenizes the card via Clover's iframe/API and sends
        the token here. Card data is NOT stored in Odoo.

        :return: Dict with success status or error message.
        :rtype: dict
        """
        if not reference:
            return {'error': 'Missing payment reference.'}

        tx_sudo = request.env['payment.transaction'].sudo().search([
            ('reference', '=', reference),
            ('provider_code', '=', 'clover'),
        ], limit=1)

        if not tx_sudo:
            return {'error': 'Transaction not found.'}

        if not card_token:
            return {'error': 'Missing card token. Please try again.'}

        try:
            if card_type:
                surcharge_fee = self._apply_portal_surcharge(tx_sudo, card_type)

            provider = tx_sudo.provider_id.sudo()
            capture = not provider.capture_manually

            result = provider._clover_create_charge(
                source_token=card_token,
                amount=tx_sudo.amount,
                currency=tx_sudo.currency_id,
                capture=capture,
                description=reference,
                ecomind='ecom',
                metadata={'odoo_reference': reference},
            )

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

            tx_sudo.write({
                'clover_charge_id': charge_id,
                'provider_reference': charge_id,
            })

            payment_data = {
                'reference': reference,
                'clover_charge_id': charge_id,
                'clover_status': status,
                'source': result.get('source', {}),
            }
            tx_sudo._process('clover', payment_data)

            return {'success': True, 'status': status}
        except ValidationError as e:
            return {'error': str(e)}
        except Exception as e:
            _logger.error("Card payment processing failed: %s", e)
            return {'error': 'Payment processing failed. Please try again.'}
