import hashlib
import hmac
import json
import logging
import uuid

import requests
from django.conf import settings

logger = logging.getLogger(__name__)

PAYSTACK_BASE = "https://api.paystack.co"


def _headers():
    return {
        "Authorization": f"Bearer {settings.PAYSTACK_SECRET_KEY}",
        "Content-Type": "application/json",
    }


def ghs_to_pesewas(amount_ghs) -> int:
    """Convert GHS decimal to Paystack pesewas integer (GHS × 100)."""
    return int(round(float(amount_ghs) * 100))


def pesewas_to_ghs(pesewas: int) -> float:
    return pesewas / 100.0


# Keep old name as alias so existing callers don't break
ghs_to_kobo = ghs_to_pesewas
kobo_to_ghs = pesewas_to_ghs


# ─────────────────────────────────────────────────────────────────────────────
# Payment Initialisation
# ─────────────────────────────────────────────────────────────────────────────

def initialize_payment(
    email: str,
    amount_ghs: float,
    reference: str,
    callback_url: str,
    metadata: dict = None,
) -> dict:
    payload = {
        "email": email,
        "amount": ghs_to_pesewas(amount_ghs),
        "reference": reference,
        "currency": "GHS",
        "callback_url": callback_url,
        "metadata": metadata or {},
        "channels": ["card", "bank", "mobile_money", "ussd", "bank_transfer"],
    }
    resp = requests.post(
        f"{PAYSTACK_BASE}/transaction/initialize",
        headers=_headers(),
        json=payload,
        timeout=15,
    )
    resp.raise_for_status()
    data = resp.json()
    if not data.get("status"):
        raise ValueError(f"Paystack init failed: {data.get('message')}")
    return data["data"]


def verify_payment(reference: str) -> dict:
    resp = requests.get(
        f"{PAYSTACK_BASE}/transaction/verify/{reference}",
        headers=_headers(),
        timeout=15,
    )
    resp.raise_for_status()
    data = resp.json()
    if not data.get("status"):
        raise ValueError(f"Paystack verify failed: {data.get('message')}")
    return data["data"]


# ─────────────────────────────────────────────────────────────────────────────
# Refunds  (buyer refund on dispute / admin action)
# ─────────────────────────────────────────────────────────────────────────────

def create_refund(transaction_reference: str, amount_ghs: float = None) -> dict:
    """
    Initiate a refund for a transaction.

    Args:
        transaction_reference: The Paystack transaction reference or ID.
        amount_ghs: Amount to refund in GHS. If None, full refund is issued.

    Returns:
        Paystack refund data dict.

    Raises:
        ValueError / requests.HTTPError on failure.
    """
    payload: dict = {"transaction": transaction_reference}
    if amount_ghs is not None:
        payload["amount"] = ghs_to_pesewas(amount_ghs)
        payload["currency"] = "GHS"

    resp = requests.post(
        f"{PAYSTACK_BASE}/refund",
        headers=_headers(),
        json=payload,
        timeout=15,
    )
    resp.raise_for_status()
    data = resp.json()
    if not data.get("status"):
        raise ValueError(f"Paystack refund failed: {data.get('message')}")
    return data["data"]


def get_refund(refund_id: str) -> dict:
    """Fetch a single refund by ID."""
    resp = requests.get(
        f"{PAYSTACK_BASE}/refund/{refund_id}",
        headers=_headers(),
        timeout=15,
    )
    resp.raise_for_status()
    return resp.json().get("data", {})


# ─────────────────────────────────────────────────────────────────────────────
# Transfer Recipients (Vendor Payout Accounts)
# ─────────────────────────────────────────────────────────────────────────────

def create_transfer_recipient_bank(
    account_name: str,
    account_number: str,
    bank_code: str,
) -> str:
    payload = {
        "type": "nuban",
        "name": account_name,
        "account_number": account_number,
        "bank_code": bank_code,
        "currency": "GHS",
    }
    resp = requests.post(
        f"{PAYSTACK_BASE}/transferrecipient",
        headers=_headers(),
        json=payload,
        timeout=15,
    )
    resp.raise_for_status()
    data = resp.json()
    if not data.get("status"):
        raise ValueError(f"Paystack recipient creation failed: {data.get('message')}")
    return data["data"]["recipient_code"]


def create_transfer_recipient_mobile_money(
    account_name: str,
    phone: str,
    network: str,
) -> str:
    payload = {
        "type": "mobile_money",
        "name": account_name,
        "account_number": phone,
        "bank_code": network,
        "currency": "GHS",
    }
    resp = requests.post(
        f"{PAYSTACK_BASE}/transferrecipient",
        headers=_headers(),
        json=payload,
        timeout=15,
    )
    resp.raise_for_status()
    data = resp.json()
    if not data.get("status"):
        raise ValueError(f"Paystack mobile money recipient failed: {data.get('message')}")
    return data["data"]["recipient_code"]


def list_banks_ghana() -> list:
    resp = requests.get(
        f"{PAYSTACK_BASE}/bank?country=ghana&currency=GHS",
        headers=_headers(),
        timeout=15,
    )
    resp.raise_for_status()
    return resp.json().get("data", [])


# ─────────────────────────────────────────────────────────────────────────────
# Transfers (Payouts to Vendors)
# NOTE: You MUST disable OTP for transfers in your Paystack Dashboard:
#       Settings → Preferences → Transfer → uncheck "Confirm transfers before sending"
# ─────────────────────────────────────────────────────────────────────────────

def initiate_transfer(
    recipient_code: str,
    amount_ghs: float,
    reason: str,
    reference: str = None,
) -> dict:
    """
    Send money to a Paystack transfer recipient.

    Requires OTP to be disabled on the Paystack dashboard for automation.
    Amount is in GHS — converted to pesewas internally.

    Returns the transfer data dict with at minimum:
        transfer_code, status (pending|otp|success|failed)
    """
    ref = reference or f"ACA-PAYOUT-{uuid.uuid4().hex[:12].upper()}"
    payload = {
        "source": "balance",
        "amount": ghs_to_pesewas(amount_ghs),
        "recipient": recipient_code,
        "reason": reason,
        "currency": "GHS",
        "reference": ref,
    }
    resp = requests.post(
        f"{PAYSTACK_BASE}/transfer",
        headers=_headers(),
        json=payload,
        timeout=15,
    )
    resp.raise_for_status()
    data = resp.json()
    if not data.get("status"):
        raise ValueError(f"Paystack transfer failed: {data.get('message')}")
    transfer = data["data"]

    # Warn if OTP is still required — the transfer won't complete automatically
    if transfer.get("status") == "otp":
        logger.warning(
            "Paystack transfer %s requires OTP — disable OTP in Paystack Dashboard "
            "(Settings → Preferences → uncheck 'Confirm transfers before sending')",
            transfer.get("transfer_code"),
        )
    return transfer


def verify_transfer(transfer_code: str) -> dict:
    resp = requests.get(
        f"{PAYSTACK_BASE}/transfer/{transfer_code}",
        headers=_headers(),
        timeout=15,
    )
    resp.raise_for_status()
    return resp.json().get("data", {})


def retry_transfer(transfer_code: str) -> dict:
    """
    Retry a previously failed transfer using the same transfer code.
    Use this instead of creating a new transfer to avoid double-crediting.
    """
    resp = requests.get(
        f"{PAYSTACK_BASE}/transfer/{transfer_code}",
        headers=_headers(),
        timeout=15,
    )
    resp.raise_for_status()
    existing = resp.json().get("data", {})
    status = existing.get("status", "")

    if status in ("success", "pending"):
        # Already going through, no retry needed
        return existing

    # Re-initiate with the same reference to let Paystack deduplicate
    recipient = existing.get("recipient", {}).get("recipient_code", "")
    amount_pesewas = existing.get("amount", 0)
    reason = existing.get("reason", "ACA payout retry")
    reference = existing.get("reference", f"ACA-RETRY-{uuid.uuid4().hex[:8].upper()}")

    if not recipient or not amount_pesewas:
        raise ValueError(f"Cannot retry transfer {transfer_code}: missing recipient or amount")

    payload = {
        "source": "balance",
        "amount": amount_pesewas,
        "recipient": recipient,
        "reason": reason,
        "currency": "GHS",
        "reference": reference,
    }
    retry_resp = requests.post(
        f"{PAYSTACK_BASE}/transfer",
        headers=_headers(),
        json=payload,
        timeout=15,
    )
    retry_resp.raise_for_status()
    data = retry_resp.json()
    if not data.get("status"):
        raise ValueError(f"Paystack transfer retry failed: {data.get('message')}")
    return data["data"]


# ─────────────────────────────────────────────────────────────────────────────
# Webhook Signature Verification
# ─────────────────────────────────────────────────────────────────────────────

def verify_webhook_signature(payload_bytes: bytes, signature: str) -> bool:
    secret = settings.PAYSTACK_SECRET_KEY.encode("utf-8")
    computed = hmac.new(secret, payload_bytes, hashlib.sha512).hexdigest()
    return hmac.compare_digest(computed, signature)