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

import json
import time
import uuid

from odoo.exceptions import ValidationError

from odoo.addons.fusion_poynt import const


def generate_request_id():
    """Generate a unique request ID for Poynt API idempotency."""
    return str(uuid.uuid4())


def build_api_url(endpoint, business_id=None, store_id=None, is_test=False):
    """Build a full Poynt API URL for the given endpoint.

    :param str endpoint: The API endpoint path (e.g., 'orders', 'transactions').
    :param str business_id: The merchant's business UUID.
    :param str store_id: The store UUID (optional, for store-scoped endpoints).
    :param bool is_test: Whether to use the test environment.
    :return: The full API URL.
    :rtype: str
    """
    base = const.API_BASE_URL_TEST if is_test else const.API_BASE_URL

    if business_id and store_id:
        return f"{base}/businesses/{business_id}/stores/{store_id}/{endpoint}"
    elif business_id:
        return f"{base}/businesses/{business_id}/{endpoint}"
    return f"{base}/{endpoint}"


def build_api_headers(access_token, request_id=None):
    """Build the standard HTTP headers for a Poynt API request.

    :param str access_token: The OAuth2 bearer token.
    :param str request_id: Optional unique request ID for idempotency.
    :return: The request headers dict.
    :rtype: dict
    """
    if not request_id:
        request_id = generate_request_id()
    headers = {
        'Content-Type': 'application/json',
        'api-version': const.API_VERSION,
        'Accept': 'application/json',
        'Authorization': f'Bearer {access_token}',
        'Poynt-Request-Id': request_id,
    }
    return headers


def clean_application_id(raw_app_id):
    """Extract the urn:aid:... portion from a raw application ID string.

    Poynt developer portal sometimes displays the app UUID and URN together
    (e.g. 'a73a2957-...=urn:aid:fb0ba879-...'). The JWT needs only the URN.

    :param str raw_app_id: The raw application ID string.
    :return: The cleaned application ID (urn:aid:...).
    :rtype: str
    """
    if not raw_app_id:
        return raw_app_id
    raw_app_id = raw_app_id.strip()
    if 'urn:aid:' in raw_app_id:
        idx = raw_app_id.index('urn:aid:')
        return raw_app_id[idx:]
    return raw_app_id


def create_self_signed_jwt(application_id, private_key_pem):
    """Create a self-signed JWT for Poynt OAuth2 token request.

    The JWT is signed with the application's RSA private key and used
    as the assertion in the JWT bearer grant type flow.

    :param str application_id: The Poynt application ID (urn:aid:...).
    :param str private_key_pem: PEM-encoded RSA private key string.
    :return: The signed JWT string.
    :rtype: str
    :raises ValidationError: If JWT creation fails.
    """
    try:
        import jwt as pyjwt
        from cryptography.hazmat.primitives.serialization import load_pem_private_key
        from cryptography.hazmat.backends import default_backend
    except ImportError:
        raise ValidationError(
            "Required Python packages 'PyJWT' and 'cryptography' are not installed. "
            "Install them with: pip install PyJWT cryptography"
        )

    try:
        if isinstance(private_key_pem, bytes):
            key_bytes = private_key_pem
        else:
            key_bytes = private_key_pem.encode('utf-8')

        private_key = load_pem_private_key(key_bytes, password=None, backend=default_backend())

        app_id = clean_application_id(application_id)

        now = int(time.time())
        payload = {
            'iss': app_id,
            'sub': app_id,
            'aud': 'https://services.poynt.net',
            'iat': now,
            'exp': now + 300,
            'jti': str(uuid.uuid4()),
        }

        token = pyjwt.encode(payload, private_key, algorithm='RS256')
        return token
    except Exception as e:
        raise ValidationError(
            f"Failed to create self-signed JWT for Poynt authentication: {e}"
        )


def format_poynt_amount(amount, currency):
    """Convert a major currency amount to Poynt's minor units (cents).

    :param float amount: The amount in major currency units.
    :param recordset currency: The currency record.
    :return: The amount in minor currency units (integer).
    :rtype: int
    """
    decimals = const.CURRENCY_DECIMALS.get(currency.name, 2)
    return int(round(amount * (10 ** decimals)))


def parse_poynt_amount(minor_amount, currency):
    """Convert Poynt's minor currency units back to major units.

    :param int minor_amount: The amount in minor currency units.
    :param recordset currency: The currency record.
    :return: The amount in major currency units.
    :rtype: float
    """
    decimals = const.CURRENCY_DECIMALS.get(currency.name, 2)
    return minor_amount / (10 ** decimals)


def extract_card_details(funding_source):
    """Extract card details from a Poynt funding source object.

    :param dict funding_source: The Poynt fundingSource object from a transaction.
    :return: Dict with card brand, last4, expiration, and card type.
    :rtype: dict
    """
    if not funding_source or 'card' not in funding_source:
        return {}

    card = funding_source['card']
    brand_code = const.CARD_BRAND_MAPPING.get(
        card.get('type', ''), 'card'
    )

    return {
        'brand': brand_code,
        'last4': card.get('numberLast4', ''),
        'exp_month': card.get('expirationMonth'),
        'exp_year': card.get('expirationYear'),
        'card_holder': card.get('cardHolderFullName', ''),
        'card_id': card.get('cardId', ''),
        'number_first6': card.get('numberFirst6', ''),
    }


def get_poynt_status(status_str):
    """Map a Poynt transaction status string to an Odoo transaction state.

    :param str status_str: The Poynt transaction status.
    :return: The corresponding Odoo payment state.
    :rtype: str
    """
    for odoo_state, poynt_statuses in const.STATUS_MAPPING.items():
        if status_str in poynt_statuses:
            return odoo_state
    return 'error'


def build_order_payload(reference, amount, currency, business_id='',
                        store_id='', items=None, notes=''):
    """Build a Poynt order creation payload.

    :param str reference: The Odoo transaction reference.
    :param float amount: The order total in major currency units.
    :param recordset currency: The currency record.
    :param str business_id: The Poynt business UUID.
    :param str store_id: The Poynt store UUID.
    :param list items: Optional list of order item dicts.
    :param str notes: Optional order notes.
    :return: The Poynt-formatted order payload.
    :rtype: dict
    """
    minor_amount = format_poynt_amount(amount, currency)

    if not items:
        items = [{
            'name': reference,
            'quantity': 1,
            'unitPrice': minor_amount,
            'tax': 0,
            'status': 'ORDERED',
            'unitOfMeasure': 'EACH',
        }]

    context = {
        'source': 'WEB',
        'sourceApp': 'odoo.fusion_poynt',
    }
    if business_id:
        context['businessId'] = business_id
    if store_id:
        context['storeId'] = store_id

    return {
        'items': items,
        'amounts': {
            'subTotal': minor_amount,
            'discountTotal': 0,
            'feeTotal': 0,
            'taxTotal': 0,
            'netTotal': minor_amount,
            'currency': currency.name,
        },
        'context': context,
        'statuses': {
            'status': 'OPENED',
        },
        'notes': notes or reference,
    }


def build_transaction_payload(
    action, amount, currency, order_id=None, reference='',
    funding_source=None, business_id='', store_id='',
):
    """Build a Poynt transaction payload for charge/auth/capture.

    :param str action: The transaction action (AUTHORIZE, SALE, CAPTURE, etc.).
    :param float amount: The amount in major currency units.
    :param recordset currency: The currency record.
    :param str order_id: The Poynt order UUID (optional).
    :param str reference: The Odoo transaction reference.
    :param dict funding_source: The funding source / card data (optional).
    :param str business_id: The Poynt business UUID (optional).
    :param str store_id: The Poynt store UUID (optional).
    :return: The Poynt-formatted transaction payload.
    :rtype: dict
    """
    minor_amount = format_poynt_amount(amount, currency)

    context = {
        'source': 'WEB',
        'sourceApp': 'odoo.fusion_poynt',
        'transactionInstruction': 'ONLINE_AUTH_REQUIRED',
    }
    if business_id:
        context['businessId'] = business_id
    if store_id:
        context['storeId'] = store_id

    payload = {
        'action': action,
        'amounts': {
            'transactionAmount': minor_amount,
            'orderAmount': minor_amount,
            'tipAmount': 0,
            'cashbackAmount': 0,
            'currency': currency.name,
        },
        'context': context,
        'notes': reference,
    }

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

    if funding_source:
        payload['fundingSource'] = funding_source

    return payload


def build_token_charge_payload(
    action, amount, currency, payment_jwt,
    business_id='', store_id='', reference='',
):
    """Build a payload for POST /cards/tokenize/charge.

    :param str action: SALE or AUTHORIZE.
    :param float amount: Amount in major currency units.
    :param recordset currency: Currency record.
    :param str payment_jwt: The payment token JWT from /cards/tokenize.
    :param str business_id: Poynt business UUID.
    :param str store_id: Poynt store UUID.
    :param str reference: Optional reference note.
    :return: The charge payload dict.
    :rtype: dict
    """
    minor_amount = format_poynt_amount(amount, currency)

    context = {}
    if business_id:
        context['businessId'] = business_id
    if store_id:
        context['storeId'] = store_id

    payload = {
        'action': action,
        'context': context,
        'amounts': {
            'transactionAmount': minor_amount,
            'orderAmount': minor_amount,
            'currency': currency.name,
        },
        'fundingSource': {
            'cardToken': payment_jwt,
        },
    }
    if reference:
        payload['notes'] = reference
    return payload
