import json
from decimal import Decimal
from django.conf import settings
from django.urls import reverse
from django.utils.encoding import force_str
import requests

from .models import PaymentTransaction, WalletTransaction, create_notification


class PaymentProviderError(Exception):
    pass


class BasePaymentProvider:
    provider_name = None

    def __init__(self, request):
        self.request = request
        self.secret_key = getattr(settings, f"{self.provider_name.upper()}_SECRET_KEY", "")
        if not self.secret_key:
            raise PaymentProviderError(f"Missing secret key for {self.provider_name}.")

    def get_return_url(self, transaction):
        return self.request.build_absolute_uri(
            reverse("core:payment_return", args=[self.provider_name, transaction.pk])
        )

    def get_webhook_url(self):
        return self.request.build_absolute_uri(
            reverse("core:payment_webhook", args=[self.provider_name])
        )

    def create_payment(self, transaction: PaymentTransaction):
        raise NotImplementedError

    def verify_payment(self, transaction: PaymentTransaction, request=None):
        return None

    def handle_webhook(self, payload):
        raise NotImplementedError


class StripeProvider(BasePaymentProvider):
    provider_name = "stripe"
    endpoint = "https://api.stripe.com/v1/checkout/sessions"

    def create_payment(self, transaction: PaymentTransaction):
        payload = {
            "client_reference_id": str(transaction.reference),
            "customer_email": transaction.user.email,
            "success_url": self.get_return_url(transaction) + "?session_id={CHECKOUT_SESSION_ID}",
            "cancel_url": self.request.build_absolute_uri(
                reverse("core:checkout_subscription", args=[transaction.plan.pk])
            ),
            "mode": "payment",
            "line_items[0][price_data][currency]": transaction.currency.lower(),
            "line_items[0][price_data][product_data][name]": transaction.plan.name,
            "line_items[0][price_data][product_data][description]": transaction.plan.description or transaction.plan.name,
            "line_items[0][price_data][unit_amount]": transaction.amount,
            "line_items[0][quantity]": 1,
        }

        response = requests.post(self.endpoint, data=payload, auth=(self.secret_key, ""))
        response_data = response.json()
        if response.status_code != 200 or "url" not in response_data:
            raise PaymentProviderError(
                f"Stripe checkout session could not be created: {response_data.get('error', response_data)}"
            )
        transaction.provider_payment_id = response_data.get("id", "")
        transaction.redirect_url = response_data["url"]
        transaction.status = PaymentTransaction.Status.PENDING
        transaction.save(update_fields=["provider_payment_id", "redirect_url", "status", "updated_at"])
        return response_data["url"]

    def verify_payment(self, transaction: PaymentTransaction, request=None):
        if not request:
            return None

        session_id = request.GET.get("session_id")
        if not session_id:
            return None

        response = requests.get(f"{self.endpoint}/{session_id}", auth=(self.secret_key, ""))
        response_data = response.json()
        if response.status_code != 200:
            raise PaymentProviderError(
                f"Stripe session verification failed: {response_data.get('error', response_data)}"
            )

        if response_data.get("payment_status") == "paid":
            transaction.mark_completed(
                provider_payment_id=force_str(response_data.get("payment_intent") or response_data.get("id")),
                metadata=response_data,
            )
            return transaction

        return None

    def handle_webhook(self, payload):
        event_type = payload.get("type")
        if event_type != "checkout.session.completed":
            return None

        session = payload.get("data", {}).get("object", {})
        reference = session.get("client_reference_id")
        if not reference:
            raise PaymentProviderError("Stripe webhook missing client_reference_id.")

        transaction = PaymentTransaction.objects.filter(reference=reference, provider=self.provider_name).first()
        if not transaction:
            raise PaymentProviderError("Stripe payment transaction not found.")

        transaction.mark_completed(provider_payment_id=force_str(session.get("payment_intent") or session.get("id")), metadata=session)
        return transaction


class PaystackProvider(BasePaymentProvider):
    provider_name = "paystack"
    endpoint = "https://api.paystack.co/transaction/initialize"

    def create_payment(self, transaction: PaymentTransaction):
        payload = {
            "email": transaction.user.email,
            "amount": transaction.amount,
            "currency": transaction.currency,
            "reference": str(transaction.reference),
            "callback_url": self.get_return_url(transaction),
        }
        headers = {
            "Authorization": f"Bearer {self.secret_key}",
            "Content-Type": "application/json",
        }

        response = requests.post(self.endpoint, json=payload, headers=headers)
        response_data = response.json()
        if not response_data.get("status") or "authorization_url" not in response_data.get("data", {}):
            raise PaymentProviderError(
                f"Paystack initialization failed: {response_data.get('message', response_data)}"
            )

        transaction.provider_payment_id = response_data["data"].get("reference", "")
        transaction.redirect_url = response_data["data"]["authorization_url"]
        transaction.status = PaymentTransaction.Status.PENDING
        transaction.save(update_fields=["provider_payment_id", "redirect_url", "status", "updated_at"])
        return transaction.redirect_url

    def verify_payment(self, transaction: PaymentTransaction, request=None):
        reference = request.GET.get("reference") if request else None
        if not reference:
            reference = str(transaction.reference)

        response = requests.get(
            f"https://api.paystack.co/transaction/verify/{reference}",
            headers={
                "Authorization": f"Bearer {self.secret_key}",
                "Content-Type": "application/json",
            },
        )
        response_data = response.json()
        if not response_data.get("status"):
            raise PaymentProviderError(
                f"Paystack verification failed: {response_data.get('message', response_data)}"
            )

        transaction_data = response_data.get("data", {})
        if transaction_data.get("status") == "success":
            transaction.mark_completed(provider_payment_id=transaction_data.get("reference"), metadata=transaction_data)
            return transaction

        return None

    def handle_webhook(self, payload):
        event = payload.get("event") or payload.get("data", {}).get("event")
        status = payload.get("data", {}).get("status")
        reference = payload.get("data", {}).get("reference")
        if status != "success" or not reference:
            return None

        transaction = PaymentTransaction.objects.filter(reference=reference, provider=self.provider_name).first()
        if not transaction:
            raise PaymentProviderError("Paystack payment transaction not found.")

        transaction.mark_completed(provider_payment_id=reference, metadata=payload.get("data", {}))
        return transaction


class FlutterwaveProvider(BasePaymentProvider):
    provider_name = "flutterwave"
    endpoint = "https://api.flutterwave.com/v3/payments"

    def create_payment(self, transaction: PaymentTransaction):
        amount = Decimal(transaction.amount) / Decimal(100)
        payload = {
            "tx_ref": str(transaction.reference),
            "amount": str(amount),
            "currency": transaction.currency,
            "redirect_url": self.get_return_url(transaction),
            "customer": {
                "email": transaction.user.email,
                "name": transaction.user.get_full_name() or transaction.user.username,
            },
            "payment_options": "card",
            "meta": {
                "plan": transaction.plan.name,
            },
        }
        headers = {
            "Authorization": f"Bearer {self.secret_key}",
            "Content-Type": "application/json",
        }

        response = requests.post(self.endpoint, json=payload, headers=headers)
        response_data = response.json()
        if response.status_code != 200 or response_data.get("status") != "success":
            raise PaymentProviderError(
                f"Flutterwave initialization failed: {response_data.get('message', response_data)}"
            )

        link = response_data.get("data", {}).get("link")
        if not link:
            raise PaymentProviderError("Flutterwave payment link not returned.")

        transaction.provider_payment_id = response_data.get("data", {}).get("id", "")
        transaction.redirect_url = link
        transaction.status = PaymentTransaction.Status.PENDING
        transaction.save(update_fields=["provider_payment_id", "redirect_url", "status", "updated_at"])
        return link

    def verify_payment(self, transaction: PaymentTransaction, request=None):
        tx_ref = request.GET.get("tx_ref") if request else None
        if not tx_ref:
            tx_ref = str(transaction.reference)

        headers = {
            "Authorization": f"Bearer {self.secret_key}",
            "Content-Type": "application/json",
        }
        response = requests.get(
            f"https://api.flutterwave.com/v3/transactions/verify_by_reference?tx_ref={tx_ref}",
            headers=headers,
        )
        response_data = response.json()
        if response.status_code != 200 or response_data.get("status") != "success":
            raise PaymentProviderError(
                f"Flutterwave verification failed: {response_data.get('message', response_data)}"
            )

        data_payload = response_data.get("data", {})
        if data_payload.get("status") == "successful":
            transaction.mark_completed(
                provider_payment_id=data_payload.get("id"),
                metadata=data_payload,
            )
            return transaction

        return None

    def handle_webhook(self, payload):
        event_name = payload.get("event")
        status = payload.get("data", {}).get("status")
        reference = payload.get("data", {}).get("tx_ref")
        if event_name != "charge.completed" or status != "successful" or not reference:
            return None

        transaction = PaymentTransaction.objects.filter(reference=reference, provider=self.provider_name).first()
        if not transaction:
            raise PaymentProviderError("Flutterwave payment transaction not found.")

        transaction.mark_completed(provider_payment_id=payload.get("data", {}).get("id"), metadata=payload.get("data", {}))
        return transaction


class PaymentProviderFactory:
    providers = {
        StripeProvider.provider_name: StripeProvider,
        PaystackProvider.provider_name: PaystackProvider,
        FlutterwaveProvider.provider_name: FlutterwaveProvider,
    }

    @classmethod
    def build(cls, provider_name, request):
        provider = cls.providers.get(provider_name)
        if not provider:
            raise PaymentProviderError(f"Unsupported payment provider: {provider_name}")
        return provider(request)


# ==================== CRYPTO PROVIDERS ====================

class CryptoProvider:
    """Handles BTC & USDT deposits with unique addresses and verification"""

    BASE_BTC_ADDRESS = "1BcCxuppjYxuaMeVHeRgYof1Y3aQDiGEmd"
    BASE_USDT_ADDRESS = "TJ...your_real_trc20_address_here"  # ← Replace with your real TRC20

    EXPLORERS = {
        "btc": "https://www.blockchain.com/explorer/transactions/btc/",
        "usdt": "https://tronscan.org/#/transaction/",
    }

    @staticmethod
    def generate_unique_address(provider, transaction):
        """Generate unique address/reference per deposit"""
        ref = str(transaction.reference)[:8]
        if provider == "btc":
            # Use memo/tag for uniqueness (real production uses HD wallet)
            return f"{CryptoProvider.BASE_BTC_ADDRESS}?memo={ref}"
        elif provider == "usdt":
            return CryptoProvider.BASE_USDT_ADDRESS
        return "Address not configured"

    @staticmethod
    def get_explorer_url(provider, tx_hash):
        """Get blockchain explorer link"""
        base = CryptoProvider.EXPLORERS.get(provider, "#")
        return f"{base}{tx_hash}" if tx_hash else "#"

    @staticmethod
    def verify_deposit(transaction, tx_hash=None):
        """
        Main verification method.
        Saves TX hash, verifies on blockchain, credits wallet.
        """
        if tx_hash:
            transaction.metadata = {**transaction.metadata, 'tx_hash': tx_hash}

        # Try auto-verification
        verified = CryptoProvider.auto_verify(transaction, tx_hash)

        if verified or not tx_hash:  # Allow manual approval
            transaction.status = WalletTransaction.Status.COMPLETED
            transaction.save()

            # Credit the wallet
            wallet = transaction.user.wallet
            wallet.deposit(transaction.amount)

            # Send notification
            create_notification(
                user=transaction.user,
                verb=f"Your {transaction.provider.upper()} deposit of ${transaction.amount/100:.2f} has been credited.",
                target_url=reverse("core:investment_dashboard")
            )
            return True

        return False

    @staticmethod
    def auto_verify(transaction, tx_hash):
        """Real blockchain verification"""
        if not tx_hash:
            return False

        provider = transaction.provider
        try:
            if provider == "btc":
                url = f"https://api.blockcypher.com/v1/btc/main/txs/{tx_hash}"
                r = requests.get(url, timeout=10)
                if r.status_code == 200:
                    data = r.json()
                    return data.get('confirmations', 0) >= 3  # 3+ confirmations

            elif provider == "usdt":
                url = f"https://api.tronscan.org/api/transaction/{tx_hash}"
                r = requests.get(url, timeout=10)
                if r.status_code == 200:
                    data = r.json()
                    return data.get('confirmed', False)
        except Exception:
            pass

        return False  # Fallback to manual approval
    
    
# Add to existing PaymentProviderFactory if needed, but we handle crypto separately