"""
Internal Octo webhook handler for Django.

Handles callback notifications from Octo's payment system.
Octo sends POST requests to the ``notify_url`` specified during ``prepare_payment``.
"""
import hashlib
import json
import logging

from django.conf import settings
from django.http import JsonResponse
from django.views import View
from django.utils.module_loading import import_string

from paytechuz.license import LicenseManager
from paytechuz.integrations.django.models import PaymentTransaction
from paytechuz.gateways.octo.client import OctoGateway
from paytechuz.gateways.octo.constants import OctoStatus

logger = logging.getLogger(__name__)


class OctoWebhook(View):
    """
    Internal Octo webhook handler.

    Reads configuration from ``settings.PAYTECHUZ['OCTO_BANK']``::

        PAYTECHUZ = {
            'OCTO_BANK': {
                'OCTO_SHOP_ID': 42125,
                'OCTO_SECRET': '...',
                'OCTO_UNIQUE_KEY': '...',
                'NOTIFY_URL': '...',
                'ACCOUNT_MODEL': 'orders.models.Order',
                'ACCOUNT_FIELD': 'id',
                'AMOUNT_FIELD': 'amount',
                'ONE_TIME_PAYMENT': True,
                'TEST_MODE': False,
            }
        }

    Signature verification:
        ``sha1(unique_key + octo_payment_UUID + status)``
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        # Validate license
        LicenseManager.validate_license_api_key()

        octo_settings = settings.PAYTECHUZ.get('OCTO_BANK', {})

        self.octo_shop_id = octo_settings.get('OCTO_SHOP_ID', '')
        self.octo_secret = octo_settings.get('OCTO_SECRET', '')
        self.unique_key = octo_settings.get('OCTO_UNIQUE_KEY', '')
        self.notify_url = octo_settings.get('NOTIFY_URL', '')
        self.is_test_mode = octo_settings.get('TEST_MODE', False)

        # Account model settings (like Payme / Click)
        account_model_path = octo_settings.get('ACCOUNT_MODEL')
        try:
            self.account_model = import_string(account_model_path)
        except ImportError:
            logger.error(
                "Could not import %s. Check PAYTECHUZ.OCTO_BANK.ACCOUNT_MODEL setting.",
                account_model_path,
            )
            if account_model_path:
                raise ImportError(f"Import error: {account_model_path}") from None

        self.account_field = octo_settings.get('ACCOUNT_FIELD', 'id')
        self.amount_field = octo_settings.get('AMOUNT_FIELD', 'amount')
        self.one_time_payment = octo_settings.get('ONE_TIME_PAYMENT', True)

        if self.is_test_mode:
            logger.warning(
                "Octo webhook is running in TEST MODE. "
                "Signature verification is DISABLED. "
                "For production: set TEST_MODE to False and provide "
                "PAYTECHUZ['OCTO_BANK']['OCTO_UNIQUE_KEY'] from the Octo team."
            )
        elif not self.unique_key:
            raise ValueError(
                "PAYTECHUZ['OCTO_BANK']['OCTO_UNIQUE_KEY'] is required in production. "
                "Get this key from the Octo team, or set TEST_MODE: True for testing."
            )

        # Initialize gateway for API calls (check_payment, cancel_payment)
        self.gateway = OctoGateway(
            octo_shop_id=self.octo_shop_id,
            octo_secret=self.octo_secret,
            notify_url=self.notify_url,
            is_test_mode=self.is_test_mode,
        )

    # ------------------------------------------------------------------
    # Overridable hooks
    # ------------------------------------------------------------------

    def successfully_payment(self, params, transaction):
        """Called when a payment succeeds. Override in subclass."""
        pass

    def cancelled_payment(self, params, transaction):
        """Called when a payment is cancelled/failed. Override in subclass."""
        pass

    # ------------------------------------------------------------------
    # HTTP dispatch
    # ------------------------------------------------------------------

    def post(self, request, *args, **kwargs):
        """Handle incoming Octo callback POST request."""
        try:
            data = json.loads(request.body)
        except (json.JSONDecodeError, Exception) as exc:
            logger.error("Octo webhook: invalid JSON body — %s", exc)
            return JsonResponse({"error": "Invalid JSON"}, status=400)

        logger.info("Octo webhook received: %s", data)

        # Verify signature (skip in test mode)
        if self.is_test_mode:
            logger.warning("Octo: Signature verification SKIPPED (test mode)")
        else:
            is_valid = self._verify_signature(
                unique_key=self.unique_key,
                uuid=data.get("octo_payment_UUID", ""),
                status=data.get("status", ""),
                signature=data.get("signature", ""),
            )
            if not is_valid:
                logger.warning("Octo webhook: invalid signature")
                return JsonResponse({"error": "Invalid signature"}, status=403)

        return self._handle_callback(data)

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    @staticmethod
    def _verify_signature(
        unique_key: str,
        uuid: str,
        status: str,
        signature: str,
    ) -> bool:
        """Verify callback signature using SHA-1."""
        raw = f"{unique_key}{uuid}{status}"
        computed = hashlib.sha1(raw.encode("utf-8")).hexdigest().upper()
        return computed == signature.upper()

    def _find_account(self, account_id):
        """Find the account (order) from ACCOUNT_MODEL by ACCOUNT_FIELD."""
        try:
            lookup_value = account_id
            if self.account_field == 'id':
                if isinstance(account_id, str) and account_id.isdigit():
                    lookup_value = int(account_id)

            lookup_kwargs = {self.account_field: lookup_value}
            return self.account_model._default_manager.get(**lookup_kwargs)
        except (self.account_model.DoesNotExist, ValueError):
            return None

    def _validate_amount(self, received_amount, expected_amount):
        """Validate that the received amount matches the expected amount."""
        received = float(received_amount)
        expected = float(expected_amount)
        if abs(received - expected) > 0.01:
            logger.warning(
                "Octo amount mismatch: received=%s, expected=%s",
                received,
                expected,
            )
            return False
        return True

    def _handle_callback(self, data: dict) -> JsonResponse:
        """Process the callback data and update the transaction."""
        shop_transaction_id = data.get("shop_transaction_id", "")
        octo_payment_uuid = data.get("octo_payment_UUID", "")
        status = data.get("status", "")
        total_sum = data.get("total_sum", 0)

        extra_data = {
            "shop_transaction_id": shop_transaction_id,
            "transfer_sum": data.get("transfer_sum"),
            "refunded_sum": data.get("refunded_sum"),
            "card_country": data.get("card_country"),
            "maskedPan": data.get("maskedPan"),
            "rrn": data.get("rrn"),
            "riskLevel": data.get("riskLevel"),
            "payed_time": data.get("payed_time"),
            "card_type": data.get("card_type"),
            "currency": data.get("currency"),
            "card_vendor": data.get("card_vendor"),
        }

        # 1. Check duplicate: if this octo_payment_UUID already processed
        try:
            transaction = PaymentTransaction._default_manager.get(
                gateway=PaymentTransaction.OCTO,
                transaction_id=octo_payment_uuid,
            )

            # Already in a final state — return immediately
            if transaction.state in (
                PaymentTransaction.SUCCESSFULLY,
                PaymentTransaction.CANCELLED,
            ):
                logger.info(
                    "Octo duplicate callback for %s, state=%s — skipping",
                    octo_payment_uuid,
                    transaction.state,
                )
                return JsonResponse({
                    "status": "ok",
                    "transaction_status": transaction.state,
                })

            # Update extra_data on existing non-final transaction
            current = transaction.extra_data or {}
            current.update(extra_data)
            transaction.extra_data = current
            transaction.save(update_fields=["extra_data", "updated_at"])

        except PaymentTransaction.DoesNotExist:
            # 2. Find account (order) from ACCOUNT_MODEL
            account = self._find_account(shop_transaction_id)
            if not account:
                logger.error(
                    "Octo webhook: account not found for shop_transaction_id=%s",
                    shop_transaction_id,
                )
                return JsonResponse({
                    "error": "Account not found",
                }, status=404)

            # 3. Validate amount against account model
            expected_amount = float(getattr(account, self.amount_field, 0))
            if self.one_time_payment and not self._validate_amount(total_sum, expected_amount):
                return JsonResponse({
                    "error": "Amount mismatch",
                }, status=400)

            # 4. Check one_time_payment: if this account already has a paid transaction
            if self.one_time_payment:
                already_paid = PaymentTransaction._default_manager.filter(
                    gateway=PaymentTransaction.OCTO,
                    account_id=str(account.id),
                    state=PaymentTransaction.SUCCESSFULLY,
                ).exists()

                if already_paid:
                    logger.warning(
                        "Octo: account %s already has a successful payment — rejecting",
                        account.id,
                    )
                    return JsonResponse({
                        "error": "Payment already completed for this account",
                    }, status=409)

            # 5. Create new transaction
            #    transaction_id  = octo_payment_UUID  (Octo's unique ID)
            #    account_id      = account.id          (merchant's order ID)
            transaction = PaymentTransaction.create_transaction(
                gateway=PaymentTransaction.OCTO,
                transaction_id=octo_payment_uuid,
                account_id=str(account.id),
                amount=total_sum,
                extra_data=extra_data,
            )

        if status == OctoStatus.SUCCEEDED:
            transaction.mark_as_paid()
            LicenseManager.decrement_usage_limit_async()
            self.successfully_payment(data, transaction)

        elif status in (OctoStatus.CANCELED, OctoStatus.FAILED):
            transaction.mark_as_cancelled()
            self.cancelled_payment(data, transaction)

        return JsonResponse({
            "status": "ok",
            "transaction_status": transaction.state,
        })
