"""
Billing domain: checkout, webhooks, subscription management.
Payment provider: paddle
"""

import json
import logging
from datetime import datetime
from functools import wraps
from pathlib import Path

from quart import Blueprint, flash, g, jsonify, redirect, render_template, request, session, url_for

from ..auth.routes import login_required
from ..core import config, execute, fetch_one

logger = logging.getLogger(__name__)


# =============================================================================
# Billing event hook system
# =============================================================================

_billing_hooks: dict[str, list] = {}


def on_billing_event(*event_types: str):
    """Decorator: register a handler for one or more billing event types."""
    def decorator(func):
        for event_type in event_types:
            _billing_hooks.setdefault(event_type, []).append(func)
        return func
    return decorator


async def _fire_hooks(event_type: str, data: dict) -> None:
    """Fire all registered hooks for an event type, isolating per-hook failures."""
    for hook in _billing_hooks.get(event_type, []):
        try:
            await hook(event_type, data)
        except Exception as e:
            logger.error("Hook %s failed for event %s: %s", hook.__name__, event_type, e)


def _paddle_client():
    """Return a configured Paddle SDK client."""
    from paddle_billing import Client, Environment, Options
    env = Environment.SANDBOX if config.PADDLE_ENVIRONMENT == "sandbox" else Environment.PRODUCTION
    return Client(config.PADDLE_API_KEY, options=Options(environment=env))


# Blueprint with its own template folder
bp = Blueprint(
    "billing",
    __name__,
    template_folder=str(Path(__file__).parent / "templates"),
    url_prefix="/billing",
)


# =============================================================================
# SQL Queries
# =============================================================================

async def get_subscription(user_id: int) -> dict | None:
    """Get user's subscription."""
    return await fetch_one(
        "SELECT * FROM subscriptions WHERE user_id = ? ORDER BY created_at DESC LIMIT 1",
        (user_id,)
    )


async def upsert_billing_customer(user_id: int, provider_customer_id: str) -> None:
    """Create or update billing customer record."""
    await execute(
        """INSERT INTO billing_customers (user_id, provider_customer_id)
           VALUES (?, ?)
           ON CONFLICT(user_id) DO UPDATE SET provider_customer_id = excluded.provider_customer_id""",
        (user_id, provider_customer_id),
    )


async def get_billing_customer(user_id: int) -> dict | None:
    """Get billing customer record for a user."""
    return await fetch_one(
        "SELECT * FROM billing_customers WHERE user_id = ?",
        (user_id,),
    )


async def upsert_subscription(
    user_id: int,
    plan: str,
    status: str,
    provider_subscription_id: str,
    current_period_end: str = None,
) -> int:
    """Create or update subscription, keyed by provider_subscription_id."""
    now = datetime.utcnow().isoformat()

    existing = await fetch_one(
        "SELECT id FROM subscriptions WHERE provider_subscription_id = ?",
        (provider_subscription_id,),
    )

    if existing:
        await execute(
            """UPDATE subscriptions
               SET plan = ?, status = ?, current_period_end = ?, updated_at = ?
               WHERE provider_subscription_id = ?""",
            (plan, status, current_period_end, now, provider_subscription_id),
        )
        return existing["id"]
    else:
        return await execute(
            """INSERT INTO subscriptions
               (user_id, plan, status, provider_subscription_id, current_period_end, created_at, updated_at)
               VALUES (?, ?, ?, ?, ?, ?, ?)""",
            (user_id, plan, status, provider_subscription_id, current_period_end, now, now),
        )


async def record_transaction(
    user_id: int,
    provider_transaction_id: str,
    type: str = "payment",
    amount_cents: int = None,
    currency: str = "USD",
    status: str = "pending",
) -> int:
    """Record a billing transaction. Idempotent on provider_transaction_id."""
    now = datetime.utcnow().isoformat()
    return await execute(
        """INSERT OR IGNORE INTO transactions
           (user_id, provider_transaction_id, type, amount_cents, currency, status, created_at)
           VALUES (?, ?, ?, ?, ?, ?, ?)""",
        (user_id, provider_transaction_id, type, amount_cents, currency, status, now),
    )



async def get_subscription_by_provider_id(subscription_id: str) -> dict | None:
    return await fetch_one(
        "SELECT * FROM subscriptions WHERE provider_subscription_id = ?",
        (subscription_id,)
    )



async def update_subscription_status(provider_subscription_id: str, status: str, **extra) -> None:
    """Update subscription status by provider subscription ID."""
    extra["updated_at"] = datetime.utcnow().isoformat()
    extra["status"] = status
    sets = ", ".join(f"{k} = ?" for k in extra)
    values = list(extra.values())

    values.append(provider_subscription_id)
    await execute(f"UPDATE subscriptions SET {sets} WHERE provider_subscription_id = ?", tuple(values))



async def can_access_feature(user_id: int, feature: str) -> bool:
    """Check if user can access a feature based on their plan."""
    sub = await get_subscription(user_id)
    plan = sub["plan"] if sub and sub["status"] in ("active", "on_trial", "cancelled") else "free"
    return feature in config.PLAN_FEATURES.get(plan, [])


async def is_within_limits(user_id: int, resource: str, current_count: int) -> bool:
    """Check if user is within their plan limits."""
    sub = await get_subscription(user_id)
    plan = sub["plan"] if sub and sub["status"] in ("active", "on_trial", "cancelled") else "free"
    limit = config.PLAN_LIMITS.get(plan, {}).get(resource, 0)
    if limit == -1:
        return True
    return current_count < limit


# =============================================================================
# Access Gating
# =============================================================================

def subscription_required(allowed=("active", "on_trial", "cancelled")):
    """Decorator to gate routes behind active subscription."""
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            if "user_id" not in session:
                return redirect(url_for("auth.login"))
            sub = await get_subscription(session["user_id"])
            if not sub or sub["status"] not in allowed:
                return redirect(url_for("billing.pricing"))
            return await func(*args, **kwargs)
        return wrapper
    return decorator


# =============================================================================
# Routes
# =============================================================================

@bp.route("/pricing")
async def pricing():
    """Pricing page."""
    user_sub = None
    if "user_id" in session:
        user_sub = await get_subscription(session["user_id"])
    return await render_template("pricing.html", subscription=user_sub)


@bp.route("/success")
@login_required
async def success():
    """Checkout success page."""
    return await render_template("success.html")



# =============================================================================
# Paddle Implementation
# =============================================================================

@bp.route("/checkout/<plan>", methods=["POST"])
@login_required
async def checkout(plan: str):
    """Create Paddle checkout via SDK."""
    price_id = config.PADDLE_PRICES.get(plan)
    if not price_id:
        await flash("Invalid plan selected.", "error")
        return redirect(url_for("billing.pricing"))

    from paddle_billing.Resources.Transactions.Operations import CreateTransaction
    txn = _paddle_client().transactions.create(
        CreateTransaction(
            items=[{"price_id": price_id, "quantity": 1}],
            custom_data={"user_id": str(g.user["id"]), "plan": plan},
            checkout={"url": f"{config.BASE_URL}/billing/success"},
        )
    )
    return redirect(txn.checkout.url)


@bp.route("/manage", methods=["POST"])
@login_required
async def manage():
    """Redirect to Paddle customer portal."""
    sub = await get_subscription(g.user["id"])
    if not sub or not sub.get("provider_subscription_id"):
        await flash("No active subscription found.", "error")
        return redirect(url_for("dashboard.settings"))

    try:
        subscription = _paddle_client().subscriptions.get(sub["provider_subscription_id"])
        portal_url = subscription.management_urls.update_payment_method
    except Exception:
        await flash("Could not reach the billing portal. Please try again or contact support.", "error")
        return redirect(url_for("dashboard.settings"))

    return redirect(portal_url)


@bp.route("/cancel", methods=["POST"])
@login_required
async def cancel():
    """Cancel subscription via Paddle SDK."""
    sub = await get_subscription(g.user["id"])
    if sub and sub.get("provider_subscription_id"):
        from paddle_billing.Resources.Subscriptions.Operations import CancelSubscription
        _paddle_client().subscriptions.cancel(
            sub["provider_subscription_id"],
            CancelSubscription(effective_from="next_billing_period"),
        )
    return redirect(url_for("dashboard.settings"))


@bp.route("/webhook/paddle", methods=["POST"])
async def webhook():
    """Handle Paddle webhooks."""
    import paddle_billing
    payload = await request.get_data()
    sig = request.headers.get("Paddle-Signature", "")

    try:
        paddle_billing.Notifications.Verifier().verify(payload, config.PADDLE_WEBHOOK_SECRET, sig)
    except Exception:
        return jsonify({"error": "Invalid signature"}), 400

    event = json.loads(payload)
    event_type = event.get("event_type")
    data = event.get("data", {})
    custom_data = data.get("custom_data", {})
    user_id = custom_data.get("user_id")

    if event_type == "subscription.activated":
        plan = custom_data.get("plan", "starter")
        uid = int(user_id) if user_id else 0
        customer_id = data.get("customer_id")
        if uid and customer_id:
            await upsert_billing_customer(uid, str(customer_id))
        await upsert_subscription(
            user_id=uid,
            plan=plan,
            status="active",
            provider_subscription_id=data.get("id", ""),
            current_period_end=data.get("current_billing_period", {}).get("ends_at"),
        )

    elif event_type == "subscription.updated":
        await update_subscription_status(
            data.get("id", ""),
            status=data.get("status", "active"),
            current_period_end=data.get("current_billing_period", {}).get("ends_at"),
        )

    elif event_type == "subscription.canceled":
        await update_subscription_status(data.get("id", ""), status="cancelled")

    elif event_type == "subscription.past_due":
        await update_subscription_status(data.get("id", ""), status="past_due")

    return jsonify({"received": True})



