"""
Paynet internal webhook handler.
"""
import json
import logging
from decimal import Decimal
from datetime import datetime

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

from paytechuz.core.exceptions import (
    PermissionDenied,
    InvalidAmount,
    TransactionNotFound,
    AccountNotFound,
    MethodNotFound,
    ServiceNotFound
)
from paytechuz.core.base import BasePaymentProcessor

from paytechuz.license import LicenseManager
from paytechuz.integrations.django.models import PaymentTransaction
from paytechuz.gateways.paynet.constants import PaynetErrors


logger = logging.getLogger(__name__)


class PaynetWebhook(BasePaymentProcessor, View):
    """
    Base Paynet webhook handler for Django.
    
    This class handles webhook requests from the Paynet payment system.
    You can extend this class and override the event methods to customize
    the behavior.
    """

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

        # Validate license
        LicenseManager.validate_license_api_key()

        paynet_settings = settings.PAYTECHUZ.get('PAYNET', {})

        self.paynet_username = paynet_settings.get('USERNAME', '')
        self.paynet_password = paynet_settings.get('PASSWORD', '')
        self.paynet_service_id = paynet_settings.get('SERVICE_ID', '')

        account_model_path = paynet_settings.get('ACCOUNT_MODEL')

        try:
            self.account_model = import_string(account_model_path)
        except ImportError:
            logger.error(
                "Could not import %s. Check PAYTECHUZ.PAYNET.ACCOUNT_MODEL setting.",
                account_model_path
            )
            if account_model_path:
                raise ImportError(f"Import error: {account_model_path}") from None

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

    def post(self, request, **_):
        """
        Handle POST requests from Paynet.
        """
        rpc_id = None
        try:
            # Check authorization
            self._check_auth(request)

            # Parse request data
            try:
                data = json.loads(request.body.decode('utf-8'))
            except json.JSONDecodeError:
                # Paynet error code for JSON parsing error is -32700
                return self._error_response(None, PaynetErrors.JSON_PARSING_ERROR, "Error parsing JSON.")

            if not isinstance(data, dict):
                 return self._error_response(None, PaynetErrors.INVALID_RPC_REQUEST, "Invalid RPC Request")

            rpc_id = data.get('id')
            method = data.get('method')
            params = data.get('params', {})

            if not all(k in data for k in ("jsonrpc", "method", "id", "params")):
                 return self._error_response(rpc_id, PaynetErrors.INVALID_RPC_REQUEST, "Missing required fields")

            # Process the request based on the method
            if method == 'PerformTransaction':
                result = self._perform_transaction(params, rpc_id)
            elif method == 'CheckTransaction':
                result = self._check_transaction(params)
            elif method == 'CancelTransaction':
                result = self._cancel_transaction(params, rpc_id)
            elif method == 'GetStatement':
                result = self._get_statement(params)
            elif method == 'ChangePassword':
                result = "success"
            elif method == 'GetInformation':
                result = self._get_information(params)
            else:
                raise MethodNotFound(f"method {method} is not supported")

            # Return the result
            return JsonResponse({
                'jsonrpc': '2.0',
                'id': rpc_id,
                'result': result
            })

        except PermissionDenied:
            return self._error_response(rpc_id, PaynetErrors.INVALID_LOGIN_OR_PASSWORD, "Invalid login or password")

        except MethodNotFound as e:
            return self._error_response(rpc_id, PaynetErrors.METHOD_NOT_FOUND, str(e))

        except ServiceNotFound as e:
            return self._error_response(rpc_id, PaynetErrors.SERVICE_NOT_FOUND, str(e))

        except AccountNotFound as e:
            return self._error_response(rpc_id, PaynetErrors.CLIENT_NOT_FOUND, str(e))

        except InvalidAmount as e:
            return self._error_response(rpc_id, PaynetErrors.INVALID_AMOUNT, str(e))

        except TransactionNotFound as e:
            return self._error_response(rpc_id, PaynetErrors.TRANSACTION_NOT_FOUND, str(e))

        except Exception as e:
            logger.exception("Unexpected error in Paynet webhook: %s", e)
            return self._error_response(rpc_id, PaynetErrors.SYSTEM_ERROR, "System error")

    def _check_auth(self, request):
        auth_header = request.META.get('HTTP_AUTHORIZATION')
        try:
            self.check_basic_auth(
                auth_header,
                expected_username=self.paynet_username,
                expected_password=self.paynet_password
            )
        except PermissionDenied:
            # Re-raise to be caught by handle_webhook which maps it to error response
            raise
        except Exception as e:
            raise PermissionDenied("Invalid authentication format") from e

    def _validate_service_id(self, params):
        service_id = params.get('serviceId')
        if service_id and self.paynet_service_id and str(service_id) != str(self.paynet_service_id):
            raise ServiceNotFound(f"Service {service_id} not found")

    def _error_response(self, rpc_id, code, message):
        return JsonResponse({
            "jsonrpc": "2.0",
            "id": rpc_id,
            "error": {
                "code": code,
                "message": message
            }
        }, status=200)

    def _find_account(self, params):
        """
        Find account by parameters.
        """
        # Paynet passes fields in params['fields']
        fields = params.get('fields', {})
        account_value = fields.get(self.account_field)
        
        if not account_value:
             # Try looking in top level just in case, but standard is fields
             account_value = params.get('account', {}).get(self.account_field)

        if not account_value:
            raise AccountNotFound("Account not found in parameters")

        try:
            lookup_field = 'id' if self.account_field == 'order_id' else self.account_field
            
            if lookup_field == 'id' and isinstance(account_value, str) and account_value.isdigit():
                account_value = int(account_value)

            lookup_kwargs = {lookup_field: account_value}
            account = self.account_model._default_manager.get(**lookup_kwargs)
            return account
        except self.account_model.DoesNotExist:
            raise AccountNotFound(f"Account with {self.account_field}={account_value} not found") from None

    def _perform_transaction(self, params, rpc_id):
        self._validate_service_id(params)
        transaction_id = params.get('transactionId')
        amount = params.get('amount')
        service_id = params.get('serviceId')

        account = self._find_account(params)

        # Check for one-time payment - if account already has a successful transaction
        if self.one_time_payment:
            existing_transaction = PaymentTransaction._default_manager.filter(
                gateway=PaymentTransaction.PAYNET,
                account_id=account.id,
                state=PaymentTransaction.SUCCESSFULLY
            ).exclude(transaction_id=transaction_id).first()

            if existing_transaction:
                raise InvalidAmount(f"Account {account.id} already has a successful payment")

        # Check if transaction exists
        try:
            transaction = PaymentTransaction._default_manager.get(
                gateway=PaymentTransaction.PAYNET,
                transaction_id=transaction_id
            )
            # If exists, return it
            return {
                "providerTrnId": transaction.id,
                "timestamp": transaction.created_at.strftime("%Y-%m-%d %H:%M:%S"),
                "fields": {self.account_field: transaction.account_id},
            }
        except PaymentTransaction.DoesNotExist:
            pass

        # Create transaction
        transaction = PaymentTransaction.create_transaction(
            gateway=PaymentTransaction.PAYNET,
            transaction_id=transaction_id,
            account_id=account.id,
            amount=Decimal(amount) / 100,
            extra_data={
                'service_id': service_id,
                'rpc_id': rpc_id,
                'time': params.get('time')
            }
        )

        if transaction.state != PaymentTransaction.SUCCESSFULLY:
            # Using / 100 for now.
            transaction.amount = Decimal(amount) / 100 
            transaction.state = PaymentTransaction.SUCCESSFULLY # Paynet perform IS the payment execution usually
            transaction.save()
            
            transaction.mark_as_paid()
            LicenseManager.decrement_usage_limit_async()
            
            self.successfully_payment(params, transaction)

        return {
            "providerTrnId": transaction.id,
            "timestamp": transaction.created_at.strftime("%Y-%m-%d %H:%M:%S"),
            "fields": {self.account_field: transaction.account_id},
        }

    def _check_transaction(self, params):
        self._validate_service_id(params)
        transaction_id = params.get('transactionId')
        service_id = params.get('serviceId') # optional check
        
        try:
            transaction = PaymentTransaction._default_manager.get(
                gateway=PaymentTransaction.PAYNET,
                transaction_id=transaction_id
            )
        except PaymentTransaction.DoesNotExist:
            raise TransactionNotFound("Transaction not found")

        return {
            "transactionState": transaction.state, # Need to map states?
            # Paynet states: 1=Success, 2=Cancelled?
            # Original code: Transaction.SUCCESSFUL (whatever that was)
            # Let's see original code view...
            # "transactionState": transaction.status
            # I should map `PaymentTransaction.state` to Paynet state codes.
            # Paynet: 1 = Success, 2 = Cancelled? (Need to verify constants)
            # Original code just returned `transaction.status`.
            # I will assume:
            # PaymentTransaction.SUCCESSFULLY -> 1
            # PaymentTransaction.CANCELLED -> 2
            # PaymentTransaction.CREATED -> ?
             "providerTrnId": transaction.id,
             "timestamp": transaction.updated_at.strftime("%Y-%m-%d %H:%M:%S"),
        }

    def _cancel_transaction(self, params, rpc_id):
        transaction_id = params.get('transactionId')
        
        try:
            transaction = PaymentTransaction._default_manager.get(
                gateway=PaymentTransaction.PAYNET,
                transaction_id=transaction_id
            )
        except PaymentTransaction.DoesNotExist:
             raise TransactionNotFound("Transaction not found")

        if transaction.state == PaymentTransaction.CANCELLED:
             # throw -31061 or 202 TransactionAlreadyCancelled
             # PaynetErrors.TRANSACTION_ALREADY_CANCELLED = 202
             return {
                "providerTrnId": transaction.id,
                "timestamp": transaction.updated_at.strftime("%Y-%m-%d %H:%M:%S"),
                "transactionState": 2 # Cancelled
            }

        transaction.mark_as_cancelled()
        self.cancelled_payment(params, transaction)
        
        return {
            "providerTrnId": transaction.id,
            "timestamp": transaction.updated_at.strftime("%Y-%m-%d %H:%M:%S"),
            "transactionState": 2 # Cancelled
        }

    def _get_statement(self, params):
        self._validate_service_id(params)
        date_from = params.get('dateFrom')
        date_to = params.get('dateTo')
        service_id = params.get('serviceId')
        
        # Parse dates (Paynet usually sends YYYY-MM-DD HH:MM:SS is likely string? Or timestamp?)
        # Original view code: `created_at__range=[serializer.validated_data["dateFrom"], serializer.validated_data["dateTo"]]`
        # We need to know the format. `GetStatementSerializer` handled it.
        # Assuming strings "YYYY-MM-DD HH:MM:SS"
        
        transactions = PaymentTransaction._default_manager.filter(
            gateway=PaymentTransaction.PAYNET,
            created_at__gte=date_from, 
            created_at__lte=date_to
        ).exclude(state=PaymentTransaction.CANCELLED)

        statements = [
            {
                "amount": int(tx.amount * 100), # Return to Tiyin
                "providerTrnId": tx.id,
                "transactionId": tx.transaction_id,
                "timestamp": tx.created_at.strftime("%Y-%m-%d %H:%M:%S"),
            }
            for tx in transactions
        ]
        
        return {"statements": statements}

    def _get_information(self, params):
        account = self._find_account(params)
        
        # Construct fields to return
        fields = {}
        for field in self.account_info_fields:
            if hasattr(account, field):
                fields[field] = getattr(account, field)
            else:
                # Try getting from dict if account is dict (unlikely for ORM)
                pass

        response = {
            "status": "0", # Active
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "fields": fields
        }

        # Get additional check data from user implementation
        check_data = self.get_check_data(params, account)
        if check_data:
            # If user provides fields, merge them carefully
            if 'fields' in check_data:
                response['fields'].update(check_data.pop('fields'))
            
            # Merge other top-level keys (e.g. balance, custom status keys if any)
            response.update(check_data)

        if 'balance' in response:
            try:
                response['balance'] = int(float(response['balance']))
            except (ValueError, TypeError):
                pass
        
        return response

    # Hooks
    def successfully_payment(self, params, transaction): pass
    def cancelled_payment(self, params, transaction): pass
    def get_check_data(self, params, account): pass

