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

import json
import secrets
from datetime import timedelta
from pathlib import Path

from paddle_billing import Client as PaddleClient
from paddle_billing import Environment, Options
from paddle_billing.Notifications import Secret, Verifier
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, get_paddle_price, utcnow, utcnow_iso
from ..i18n import get_translations


def _paddle_client() -> PaddleClient:
    """Create a Paddle SDK client. Used only for subscription management + webhook verification."""
    env = Environment.SANDBOX if config.PADDLE_ENVIRONMENT == "sandbox" else Environment.PRODUCTION
    return PaddleClient(config.PADDLE_API_KEY, options=Options(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 most recent 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 (idempotent)."""
    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 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. Finds existing by provider_subscription_id."""
    now = utcnow_iso()

    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 id = ?""",
            (plan, status, current_period_end, now, existing["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 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"] = utcnow_iso()
    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


# =============================================================================
# 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 — Paddle.js Overlay Checkout
# =============================================================================

@bp.route("/checkout/<plan>", methods=["POST"])
@login_required
async def checkout(plan: str):
    """Return JSON for Paddle.js overlay checkout."""
    price_id = await get_paddle_price(plan)
    if not price_id:
        return jsonify({"error": "Invalid plan selected."}), 400

    return jsonify({
        "items": [{"priceId": price_id, "quantity": 1}],
        "customData": {"user_id": str(g.user["id"]), "plan": plan},
        "settings": {
            "successUrl": f"{config.BASE_URL}/billing/success",
        },
    })


@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"):
        t = get_translations(g.get("lang") or "en")
        await flash(t["billing_no_subscription"], "error")
        return redirect(url_for("dashboard.settings"))

    paddle = _paddle_client()
    paddle_sub = paddle.subscriptions.get(sub["provider_subscription_id"])
    portal_url = paddle_sub.management_urls.update_payment_method
    return redirect(portal_url)


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


class _WebhookRequest:
    """Minimal wrapper satisfying paddle_billing's Request Protocol."""
    def __init__(self, body: bytes, headers):
        self.body = body
        self.headers = headers


_verifier = Verifier(maximum_variance=300)


@bp.route("/webhook/paddle", methods=["POST"])
async def webhook():
    """Handle Paddle webhooks."""
    payload = await request.get_data()

    if config.PADDLE_WEBHOOK_SECRET:
        try:
            ok = _verifier.verify(
                _WebhookRequest(payload, request.headers),
                Secret(config.PADDLE_WEBHOOK_SECRET),
            )
        except (ConnectionRefusedError, ValueError):
            ok = False
        if not ok:
            return jsonify({"error": "Invalid signature"}), 400

    try:
        event = json.loads(payload)
    except (json.JSONDecodeError, ValueError):
        return jsonify({"error": "Invalid JSON payload"}), 400
    event_type = event.get("event_type")
    data = event.get("data") or {}
    custom_data = data.get("custom_data") or {}
    user_id = custom_data.get("user_id")
    plan = custom_data.get("plan", "")

    # Store billing customer for any subscription event with a customer_id
    customer_id = str(data.get("customer_id", ""))
    if customer_id and user_id:
        await upsert_billing_customer(int(user_id), customer_id)

    if event_type == "subscription.activated":
        if plan.startswith("supplier_"):
            await _handle_supplier_subscription_activated(data, custom_data)
        elif user_id:
            await upsert_subscription(
                user_id=int(user_id),
                plan=plan or "starter",
                status="active",
                provider_subscription_id=data.get("id", ""),
                current_period_end=(data.get("current_billing_period") or {}).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") or {}).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")

    elif event_type == "transaction.completed":
        await _handle_transaction_completed(data, custom_data)

    return jsonify({"received": True})


# =============================================================================
# Supplier Webhook Handlers
# =============================================================================

# Map product keys to credit pack amounts
CREDIT_PACK_AMOUNTS = {
    "credits_25": 25,
    "credits_50": 50,
    "credits_100": 100,
    "credits_250": 250,
}

PLAN_MONTHLY_CREDITS = {"supplier_basic": 0, "supplier_growth": 30, "supplier_pro": 100}

BOOST_PRICE_KEYS = {
    "boost_logo": "logo",
    "boost_highlight": "highlight",
    "boost_verified": "verified",
    "boost_card_color": "card_color",
    "boost_sticky_week": "sticky_week",
    "boost_sticky_month": "sticky_month",
}


async def _price_id_to_key(price_id: str) -> str | None:
    """Reverse-lookup a paddle_products key from a Paddle price ID."""
    row = await fetch_one(
        "SELECT key FROM paddle_products WHERE paddle_price_id = ?", (price_id,)
    )
    return row["key"] if row else None


def _derive_tier_from_plan(plan: str) -> tuple[str, str]:
    """Derive (base_plan, tier) from a plan key, stripping _monthly/_yearly suffixes.

    Returns (base_plan, tier) where base_plan is the canonical key used for
    PLAN_MONTHLY_CREDITS lookup and tier is the DB tier string.
    """
    base = plan
    for suffix in ("_monthly", "_yearly"):
        if plan.endswith(suffix):
            base = plan[: -len(suffix)]
            break

    if base == "supplier_pro":
        tier = "pro"
    elif base == "supplier_basic":
        tier = "basic"
    else:
        tier = "growth"

    return base, tier


async def _handle_supplier_subscription_activated(data: dict, custom_data: dict) -> None:
    """Handle supplier plan subscription activation."""
    from ..core import transaction as db_transaction

    supplier_id = custom_data.get("supplier_id")
    plan = custom_data.get("plan", "supplier_growth")
    user_id = custom_data.get("user_id")

    if not supplier_id:
        return

    base_plan, tier = _derive_tier_from_plan(plan)
    monthly_credits = PLAN_MONTHLY_CREDITS.get(base_plan, 0)
    now = utcnow_iso()

    async with db_transaction() as db:
        # Update supplier record — Basic tier also gets is_verified = 1
        await db.execute(
            """UPDATE suppliers SET tier = ?, claimed_at = ?, claimed_by = ?,
               monthly_credits = ?, credit_balance = ?, last_credit_refill = ?,
               is_verified = 1
               WHERE id = ?""",
            (tier, now, int(user_id) if user_id else None,
             monthly_credits, monthly_credits, now, int(supplier_id)),
        )

        # Initial credit allocation — skip for Basic (0 credits)
        if monthly_credits > 0:
            await db.execute(
                """INSERT INTO credit_ledger (supplier_id, delta, balance_after, event_type, note, created_at)
                   VALUES (?, ?, ?, 'monthly_allocation', 'Initial credit allocation', ?)""",
                (int(supplier_id), monthly_credits, monthly_credits, now),
            )

        # Create boost records for items included in the subscription
        items = data.get("items", [])
        for item in items:
            price_id = item.get("price", {}).get("id", "")
            key = await _price_id_to_key(price_id)
            if key in BOOST_PRICE_KEYS:
                boost_type = BOOST_PRICE_KEYS[key]
                await db.execute(
                    """INSERT INTO supplier_boosts
                       (supplier_id, boost_type, paddle_subscription_id, status, starts_at, created_at)
                       VALUES (?, ?, ?, 'active', ?, ?)""",
                    (int(supplier_id), boost_type, data.get("id", ""), now, now),
                )
                # Update denormalized columns
                if boost_type == "highlight":
                    await db.execute(
                        "UPDATE suppliers SET highlight = 1 WHERE id = ?", (int(supplier_id),)
                    )
                elif boost_type == "verified":
                    await db.execute(
                        "UPDATE suppliers SET is_verified = 1 WHERE id = ?", (int(supplier_id),)
                    )


async def _handle_transaction_completed(data: dict, custom_data: dict) -> None:
    """Handle one-time transaction completion (credit packs, sticky boosts, business plan)."""
    supplier_id = custom_data.get("supplier_id")
    user_id = custom_data.get("user_id")
    now = utcnow_iso()

    items = data.get("items", [])
    for item in items:
        price_id = item.get("price", {}).get("id", "")
        key = await _price_id_to_key(price_id)

        if not key:
            continue

        # Credit pack purchases
        if key in CREDIT_PACK_AMOUNTS and supplier_id:
            from ..credits import add_credits
            await add_credits(
                int(supplier_id), CREDIT_PACK_AMOUNTS[key],
                "pack_purchase", note=f"Credit pack: {key}",
            )

        # Sticky boost purchases
        elif key == "boost_sticky_week" and supplier_id:
            from ..core import transaction as db_transaction
            expires = (utcnow() + timedelta(weeks=1)).strftime("%Y-%m-%d %H:%M:%S")
            country = custom_data.get("sticky_country", "")
            async with db_transaction() as db:
                await db.execute(
                    """INSERT INTO supplier_boosts
                       (supplier_id, boost_type, status, starts_at, expires_at, created_at)
                       VALUES (?, 'sticky_week', 'active', ?, ?, ?)""",
                    (int(supplier_id), now, expires, now),
                )
                await db.execute(
                    "UPDATE suppliers SET sticky_until = ?, sticky_country = ? WHERE id = ?",
                    (expires, country, int(supplier_id)),
                )

        elif key == "boost_sticky_month" and supplier_id:
            from ..core import transaction as db_transaction
            expires = (utcnow() + timedelta(days=30)).strftime("%Y-%m-%d %H:%M:%S")
            country = custom_data.get("sticky_country", "")
            async with db_transaction() as db:
                await db.execute(
                    """INSERT INTO supplier_boosts
                       (supplier_id, boost_type, status, starts_at, expires_at, created_at)
                       VALUES (?, 'sticky_month', 'active', ?, ?, ?)""",
                    (int(supplier_id), now, expires, now),
                )
                await db.execute(
                    "UPDATE suppliers SET sticky_until = ?, sticky_country = ? WHERE id = ?",
                    (expires, country, int(supplier_id)),
                )

        # Business plan PDF purchase
        elif key == "business_plan" and user_id:
            scenario_id = custom_data.get("scenario_id")
            language = custom_data.get("language", "en")
            transaction_id = data.get("id", "")
            export_id = await execute(
                """INSERT INTO business_plan_exports
                   (user_id, scenario_id, paddle_transaction_id, language, status, token, created_at)
                   VALUES (?, ?, ?, ?, 'pending', ?, ?)""",
                (int(user_id), int(scenario_id) if scenario_id else 0,
                 transaction_id, language, secrets.token_urlsafe(16), now),
            )
            # Enqueue PDF generation
            from ..worker import enqueue
            await enqueue("generate_business_plan", {
                "export_id": export_id,
                "user_id": int(user_id),
                "scenario_id": int(scenario_id) if scenario_id else 0,
                "language": language,
            })
