"""
Billing domain: checkout, webhooks, subscription management.

Provider dispatch: PAYMENT_PROVIDER env var selects 'paddle' or 'stripe'.
Both webhook endpoints (/webhook/paddle and /webhook/stripe) stay active
regardless of the toggle — existing subscribers keep sending webhooks.
"""

import json
import secrets
from datetime import timedelta
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, get_price_id, utcnow, utcnow_iso
from ..i18n import get_translations


def _provider():
    """Return the active payment provider module."""
    if config.PAYMENT_PROVIDER == "stripe":
        from . import stripe as mod
    else:
        from . import paddle as mod
    return mod


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


# =============================================================================
# SQL Queries (provider-agnostic)
# =============================================================================

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 (provider-agnostic)
# =============================================================================

@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")


# =============================================================================
# Checkout / Manage / Cancel — dispatched to active provider
# =============================================================================

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

    payload = _provider().build_checkout_payload(
        price_id=price_id,
        custom_data={"user_id": str(g.user["id"]), "plan": plan},
        success_url=f"{config.BASE_URL}/billing/success",
    )
    return jsonify(payload)


@bp.route("/checkout/item", methods=["POST"])
@login_required
async def checkout_item():
    """Return checkout JSON for a single item (boost, credit pack, etc.).

    Used by dashboard boost/credit buttons that need a server round-trip
    for Stripe (Checkout Session creation) and work with Paddle overlay too.
    Expects JSON body: {price_key, custom_data, success_url?}
    """
    body = await request.get_json(silent=True) or {}
    price_key = body.get("price_key", "")
    custom_data = body.get("custom_data", {})
    success_url = body.get("success_url", f"{config.BASE_URL}/suppliers/dashboard?tab=boosts")

    price_id = await get_price_id(price_key)
    if not price_id:
        return jsonify({"error": "Product not configured."}), 400

    payload = _provider().build_checkout_payload(
        price_id=price_id,
        custom_data=custom_data,
        success_url=success_url,
    )
    return jsonify(payload)


@bp.route("/manage", methods=["POST"])
@login_required
async def manage():
    """Redirect to payment provider's 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"))

    portal_url = _provider().get_management_url(sub["provider_subscription_id"])
    return redirect(portal_url)


@bp.route("/cancel", methods=["POST"])
@login_required
async def cancel():
    """Cancel subscription via active payment provider."""
    sub = await get_subscription(g.user["id"])
    if sub and sub.get("provider_subscription_id"):
        _provider().cancel_subscription(sub["provider_subscription_id"])
    return redirect(url_for("dashboard.settings"))


# =============================================================================
# Paddle Webhook — always active (existing subscribers keep sending)
# =============================================================================

@bp.route("/webhook/paddle", methods=["POST"])
async def webhook_paddle():
    """Handle Paddle webhooks — always active regardless of PAYMENT_PROVIDER toggle."""
    from . import paddle as paddle_mod

    payload = await request.get_data()

    if not paddle_mod.verify_webhook(payload, request.headers):
        return jsonify({"error": "Invalid signature"}), 400

    try:
        ev = paddle_mod.parse_webhook(payload)
    except (json.JSONDecodeError, ValueError):
        return jsonify({"error": "Invalid JSON payload"}), 400

    await _handle_webhook_event(ev)
    return jsonify({"received": True})


# =============================================================================
# Stripe Webhook — always active (once Stripe is configured)
# =============================================================================

@bp.route("/webhook/stripe", methods=["POST"])
async def webhook_stripe():
    """Handle Stripe webhooks — always active regardless of PAYMENT_PROVIDER toggle."""
    if not config.STRIPE_WEBHOOK_SECRET:
        return jsonify({"error": "Stripe not configured"}), 404

    from . import stripe as stripe_mod

    payload = await request.get_data()

    if not stripe_mod.verify_webhook(payload, request.headers):
        return jsonify({"error": "Invalid signature"}), 400

    try:
        ev = stripe_mod.parse_webhook(payload)
    except (json.JSONDecodeError, ValueError):
        return jsonify({"error": "Invalid payload"}), 400

    await _handle_webhook_event(ev)
    return jsonify({"received": True})


# =============================================================================
# Shared Webhook Event Handler (provider-agnostic)
# =============================================================================

async def _handle_webhook_event(ev: dict) -> None:
    """Process a normalized webhook event from any provider.

    ev keys: event_type, subscription_id, customer_id, user_id, supplier_id,
             plan, status, current_period_end, data, items, custom_data
    """
    event_type = ev.get("event_type", "")
    user_id = ev.get("user_id")
    plan = ev.get("plan", "")

    # Store billing customer
    customer_id = ev.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(ev)
        elif user_id:
            await upsert_subscription(
                user_id=int(user_id),
                plan=plan or "starter",
                status="active",
                provider_subscription_id=ev.get("subscription_id", ""),
                current_period_end=ev.get("current_period_end"),
            )

    elif event_type == "subscription.updated":
        await update_subscription_status(
            ev.get("subscription_id", ""),
            status=ev.get("status", "active"),
            current_period_end=ev.get("current_period_end"),
        )

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

    elif event_type == "subscription.past_due":
        await update_subscription_status(ev.get("subscription_id", ""), status="past_due")

    elif event_type == "transaction.completed":
        await _handle_transaction_completed(ev)


# =============================================================================
# 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 product key from a provider price ID."""
    row = await fetch_one(
        "SELECT key FROM payment_products WHERE provider_price_id = ?", (price_id,)
    )
    if row:
        return row["key"]
    # Fallback to old table for pre-migration DBs
    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(ev: dict) -> None:
    """Handle supplier plan subscription activation."""
    from ..core import transaction as db_transaction

    supplier_id = ev.get("supplier_id")
    plan = ev.get("plan", "supplier_growth")
    user_id = ev.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 = ev.get("items", [])
        data = ev.get("data", {})
        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(ev: dict) -> None:
    """Handle one-time transaction completion (credit packs, sticky boosts, business plan)."""
    supplier_id = ev.get("supplier_id")
    user_id = ev.get("user_id")
    custom_data = ev.get("custom_data", {})
    data = ev.get("data", {})
    now = utcnow_iso()

    items = ev.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,
            })
