"""
Pluggable payment layer.

A provider implements `create_checkout()` (start a payment, return where to send
the user) and `verify_callback()` (confirm a payment from the provider's redirect
or webhook). The default `MockProvider` auto-approves so the whole flow is testable
offline; swap in a real provider for production.

To go live, implement a provider for your gateway (South African options:
Payfast, Paystack, Yoco; or Stripe) using its API keys from environment variables,
and verify the webhook signature in `verify_callback`. NEVER hardcode secret keys.
"""

from __future__ import annotations

import os
import time
import uuid


class Provider:
    name = "base"

    def create_checkout(self, *, amount, currency, email, ref, plan=None) -> dict:
        """Return {'url': <where to send the user>, 'provider_ref': <id>}."""
        raise NotImplementedError

    def verify_callback(self, args: dict) -> dict:
        """Given the redirect/webhook params, return {'ref','paid':bool}."""
        raise NotImplementedError

    def verify_webhook(self, raw_body: bytes, headers) -> bool:
        """Verify a webhook is authentic (signature). Default: not verifiable."""
        return False


class MockProvider(Provider):
    """Test gateway: 'checkout' is a local confirm page that marks the payment paid.
    Use ONLY for development — it does not take real money."""
    name = "mock"

    def create_checkout(self, *, amount, currency, email, ref, plan=None):
        # send the user to our own confirm route (simulates the gateway redirect)
        return {"url": f"/pay/confirm?ref={ref}", "provider_ref": ref}

    def verify_callback(self, args):
        return {"ref": args.get("ref"), "paid": True}


# --- real-provider skeletons (fill in with your gateway's SDK + env keys) ----
class StripeProvider(Provider):  # pragma: no cover - needs Stripe account/keys
    name = "stripe"

    def __init__(self):
        self.secret = os.environ.get("STRIPE_SECRET_KEY")
        self.price_id = os.environ.get("STRIPE_PRICE_ID")
        self.webhook_secret = os.environ.get("STRIPE_WEBHOOK_SECRET")

    def create_checkout(self, *, amount, currency, email, ref):
        import stripe
        stripe.api_key = self.secret
        sess = stripe.checkout.Session.create(
            mode="subscription", customer_email=email,
            line_items=[{"price": self.price_id, "quantity": 1}],
            success_url=os.environ.get("PORTAL_URL", "") + "/pay/return?ref=" + ref,
            cancel_url=os.environ.get("PORTAL_URL", "") + "/account",
            client_reference_id=ref)
        return {"url": sess.url, "provider_ref": sess.id}

    def verify_callback(self, args):
        # In production, verify the Stripe webhook signature here and read the event.
        raise NotImplementedError("wire Stripe webhook verification before going live")


class PaystackProvider(Provider):  # pragma: no cover - needs Paystack account/keys
    """
    Paystack (South Africa). Reads keys from the environment ONLY:
      PAYSTACK_SECRET_KEY   sk_live_... or sk_test_...   (server-side secret)
      PAYSTACK_PUBLIC_KEY   pk_live_... or pk_test_...   (safe to expose)
      PORTAL_URL            public base URL of the portal (for the callback)
    Annual = one-off charge; monthly debit order = a Paystack Plan + subscription.
    """
    name = "paystack"
    API = "https://api.paystack.co"

    def __init__(self):
        self.secret = os.environ.get("PAYSTACK_SECRET_KEY", "")
        self.public = os.environ.get("PAYSTACK_PUBLIC_KEY", "")
        self.portal = os.environ.get("PORTAL_URL", "").rstrip("/")
        self.plan_codes = {  # optional: Paystack Plan codes for recurring debit
            "monthly": os.environ.get("PAYSTACK_PLAN_MONTHLY", ""),
            "annual": os.environ.get("PAYSTACK_PLAN_ANNUAL", ""),
        }

    def _headers(self):
        return {"Authorization": f"Bearer {self.secret}",
                "Content-Type": "application/json"}

    def create_checkout(self, *, amount, currency, email, ref, plan=None):
        import requests
        body = {"email": email, "amount": int(amount), "currency": currency,
                "reference": ref,
                "callback_url": f"{self.portal}/pay/return?ref={ref}"}
        code = self.plan_codes.get(plan or "")
        if code:                       # recurring (monthly debit order / annual)
            body["plan"] = code
        r = requests.post(f"{self.API}/transaction/initialize",
                          json=body, headers=self._headers(), timeout=20)
        data = r.json()["data"]
        return {"url": data["authorization_url"], "provider_ref": data["reference"]}

    def verify_callback(self, args):
        """Verify a transaction by reference against the Paystack API (trusted source)."""
        import requests
        ref = args.get("ref") or args.get("reference") or args.get("trxref")
        if not ref:
            return {"ref": None, "paid": False}
        r = requests.get(f"{self.API}/transaction/verify/{ref}",
                         headers=self._headers(), timeout=20)
        d = r.json().get("data", {})
        return {"ref": ref, "paid": d.get("status") == "success"}

    def verify_webhook(self, raw_body: bytes, headers) -> bool:
        """Paystack signs webhooks with HMAC-SHA512 over the raw body using your secret."""
        import hmac, hashlib
        sig = headers.get("x-paystack-signature", "")
        expected = hmac.new(self.secret.encode(), raw_body, hashlib.sha512).hexdigest()
        return hmac.compare_digest(sig, expected)


def get_provider() -> Provider:
    name = os.environ.get("BIOSYNC_PAYMENT_PROVIDER", "mock").lower()
    return {"mock": MockProvider, "stripe": StripeProvider,
            "paystack": PaystackProvider}.get(name, MockProvider)()


def new_ref() -> str:
    return "pay_" + uuid.uuid4().hex[:16]
