"""
Planner domain: padel court financial planner + scenario management.
"""

import json
import logging
import math
from pathlib import Path

from quart import Blueprint, Response, g, jsonify, render_template, request

from ..auth.routes import login_required
from ..core import (
    config,
    count_where,
    csrf_protect,
    execute,
    feature_gate,
    fetch_all,
    fetch_one,
    get_price_id,
    utcnow_iso,
)
from ..i18n import get_translations
from .calculator import COUNTRY_CURRENCY, CURRENCY_DEFAULT, calc, validate_state

logger = logging.getLogger(__name__)

bp = Blueprint(
    "planner",
    __name__,
    template_folder=str(Path(__file__).parent / "templates"),
    url_prefix="/planner",
)

# Country presets (mirrors JS COUNTRY_PRESETS)
COUNTRY_PRESETS = {
    "DE": {"permitsCompliance": 12000},
    "ES": {"permitsCompliance": 25000},
    "IT": {"permitsCompliance": 18000},
    "FR": {"permitsCompliance": 15000},
    "NL": {"permitsCompliance": 10000},
    "SE": {"permitsCompliance": 8000},
    "UK": {"permitsCompliance": 10000},
    "US": {"permitsCompliance": 15000},
}

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


async def count_scenarios(user_id: int) -> int:
    return await count_where(
        "scenarios WHERE user_id = ? AND deleted_at IS NULL", (user_id,)
    )


async def get_default_scenario(user_id: int) -> dict | None:
    return await fetch_one(
        "SELECT * FROM scenarios WHERE user_id = ? AND is_default = 1 AND deleted_at IS NULL",
        (user_id,),
    )


async def get_scenarios(user_id: int) -> list[dict]:
    return await fetch_all(
        "SELECT id, name, location, is_default, created_at, updated_at FROM scenarios WHERE user_id = ? AND deleted_at IS NULL ORDER BY updated_at DESC",
        (user_id,),
    )


# =============================================================================
# Helpers
# =============================================================================


def form_to_state(form) -> dict:
    """Convert Quart ImmutableMultiDict form data to state dict."""
    data: dict = {}
    ramp = form.getlist("ramp")
    if ramp:
        data["ramp"] = [float(v) for v in ramp]
    season = form.getlist("season")
    if season:
        data["season"] = [float(v) for v in season]
    for key in form.keys():
        if key not in ("ramp", "season", "activeTab"):
            data[key] = form.get(key)
    return data


def compute_sensitivity(s: dict, d: dict) -> tuple[list[dict], list[dict]]:
    """Compute utilization and price sensitivity tables from state + calc result.

    Returns (sens_rows, price_rows) — pure computation, no side effects.
    Used by both the web planner (augment_d) and the PDF generator (get_plan_sections).
    """
    is_in = s["venue"] == "indoor"
    w_rate = d["weightedRate"]
    rev_per_hr = (
        w_rate * (1 - s["bookingFee"] / 100)
        + (s["racketRentalRate"] / 100) * s["racketQty"] * s["racketPrice"]
        + (s["ballRate"] / 100) * (s["ballPrice"] - s["ballCost"])
    )
    utils = [15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70]
    ancillary_per_court = (
        s["membershipRevPerCourt"]
        + s["fbRevPerCourt"]
        + s["coachingRevPerCourt"]
        + s["retailRevPerCourt"]
    )
    sens_rows = []
    for u in utils:
        booked = d["availHoursMonth"] * (u / 100)
        rev = booked * rev_per_hr + d["totalCourts"] * ancillary_per_court * (
            u / max(s["utilTarget"], 1)
        )
        ncf = rev - d["opex"] - d["monthlyPayment"]
        annual = ncf * (12 if is_in else 6)
        ebitda = rev - d["opex"]
        dscr = (
            (ebitda * (12 if is_in else 6)) / d["annualDebtService"]
            if d["annualDebtService"] > 0
            else 999
        )
        sens_rows.append(
            {
                "util": u,
                "rev": round(rev),
                "ncf": round(ncf),
                "annual": round(annual),
                "dscr": round(min(dscr, 99), 2),
                "is_target": u == s["utilTarget"],
            }
        )

    prices = [-20, -10, -5, 0, 5, 10, 15, 20]
    price_rows = []
    for delta in prices:
        adj_rate = w_rate * (1 + delta / 100)
        booked = d["bookedHoursMonth"]
        rev = (
            booked * adj_rate * (1 - s["bookingFee"] / 100)
            + booked
            * (
                (s["racketRentalRate"] / 100) * s["racketQty"] * s["racketPrice"]
                + (s["ballRate"] / 100) * (s["ballPrice"] - s["ballCost"])
            )
            + d["totalCourts"] * ancillary_per_court
        )
        ncf = rev - d["opex"] - d["monthlyPayment"]
        price_rows.append(
            {
                "delta": delta,
                "adj_rate": round(adj_rate),
                "rev": round(rev),
                "ncf": round(ncf),
                "is_base": delta == 0,
            }
        )
    return sens_rows, price_rows


def augment_d(d: dict, s: dict, lang: str) -> None:
    """Add display-only derived fields to calc result dict (mutates d in-place)."""
    t = get_translations(lang)
    month_keys = [
        "jan",
        "feb",
        "mar",
        "apr",
        "may",
        "jun",
        "jul",
        "aug",
        "sep",
        "oct",
        "nov",
        "dec",
    ]

    d["irr_ok"] = math.isfinite(d.get("irr", 0))

    # Chart data — full Chart.js 4.x config objects, embedded as JSON in partials
    _PALETTE = [
        "#1D4ED8",
        "#16A34A",
        "#D97706",
        "#EF4444",
        "#8B5CF6",
        "#EC4899",
        "#06B6D4",
        "#84CC16",
        "#F97316",
        "#475569",
        "#0EA5E9",
        "#A78BFA",
    ]
    _cap_items = sorted(
        [i for i in d["capexItems"] if i["amount"] > 0],
        key=lambda i: i["amount"],
        reverse=True,
    )
    d["capex_chart"] = {
        "type": "doughnut",
        "data": {
            "labels": [i["name"] for i in _cap_items],
            "datasets": [
                {
                    "data": [i["amount"] for i in _cap_items],
                    "backgroundColor": [
                        _PALETTE[i % len(_PALETTE)] for i in range(len(_cap_items))
                    ],
                    "borderWidth": 0,
                }
            ],
        },
        "options": {
            "responsive": True,
            "maintainAspectRatio": False,
            "cutout": "60%",
            "plugins": {
                "legend": {
                    "position": "right",
                    "labels": {"boxWidth": 10, "font": {"size": 12}, "padding": 8},
                }
            },
        },
    }

    ramp_data = d["months"][:24]
    d["ramp_chart"] = {
        "type": "line",
        "data": {
            "labels": [f"M{m['m']}" for m in ramp_data],
            "datasets": [
                {
                    "label": t["chart_revenue"],
                    "data": [round(m["totalRev"]) for m in ramp_data],
                    "borderColor": "#16A34A",
                    "backgroundColor": "rgba(22,163,74,0.08)",
                    "fill": True,
                    "tension": 0.35,
                    "pointRadius": 0,
                    "borderWidth": 2,
                },
                {
                    "label": t["chart_opex_debt"],
                    "data": [round(abs(m["opex"]) + abs(m["loan"])) for m in ramp_data],
                    "borderColor": "#EF4444",
                    "backgroundColor": "rgba(239,68,68,0.06)",
                    "fill": True,
                    "tension": 0.35,
                    "pointRadius": 0,
                    "borderWidth": 2,
                },
            ],
        },
        "options": {
            "responsive": True,
            "maintainAspectRatio": False,
            "plugins": {
                "legend": {"display": True, "labels": {"boxWidth": 12, "font": {"size": 10}}}
            },
            "scales": {
                "y": {"ticks": {"font": {"size": 10}}},
                "x": {"ticks": {"font": {"size": 9}}},
            },
        },
    }

    _pl_values = [
        round(d["courtRevMonth"]),
        -round(d["feeDeduction"]),
        round(
            d["racketRev"]
            + d["ballMargin"]
            + d["membershipRev"]
            + d["fbRev"]
            + d["coachingRev"]
            + d["retailRev"]
        ),
        -round(d["opex"]),
        -round(d["monthlyPayment"]),
    ]
    d["pl_chart"] = {
        "type": "bar",
        "data": {
            "labels": [
                t["chart_court_rev"],
                t["chart_fees"],
                t["chart_ancillary"],
                t["chart_opex"],
                t["chart_debt"],
            ],
            "datasets": [
                {
                    "data": _pl_values,
                    "backgroundColor": [
                        "rgba(22,163,74,0.7)" if v >= 0 else "rgba(239,68,68,0.7)"
                        for v in _pl_values
                    ],
                    "borderRadius": 4,
                }
            ],
        },
        "options": {
            "indexAxis": "y",
            "responsive": True,
            "maintainAspectRatio": False,
            "plugins": {"legend": {"display": False}},
            "scales": {
                "x": {"ticks": {"font": {"size": 9}}},
                "y": {"ticks": {"font": {"size": 10}}},
            },
        },
    }

    _cf_values = [round(m["ncf"]) for m in d["months"]]
    d["cf_chart"] = {
        "type": "bar",
        "data": {
            "labels": [f"Y{m['yr']}" if m["m"] % 12 == 1 else "" for m in d["months"]],
            "datasets": [
                {
                    "data": _cf_values,
                    "backgroundColor": [
                        "rgba(22,163,74,0.7)" if v >= 0 else "rgba(239,68,68,0.7)"
                        for v in _cf_values
                    ],
                    "borderRadius": 2,
                }
            ],
        },
        "options": {
            "responsive": True,
            "maintainAspectRatio": False,
            "plugins": {"legend": {"display": False}},
            "scales": {
                "y": {"ticks": {"font": {"size": 10}}},
                "x": {"ticks": {"font": {"size": 9}}},
            },
        },
    }

    d["cum_chart"] = {
        "type": "line",
        "data": {
            "labels": [f"M{m['m']}" if m["m"] % 6 == 1 else "" for m in d["months"]],
            "datasets": [
                {
                    "data": [round(m["cum"]) for m in d["months"]],
                    "borderColor": "#1D4ED8",
                    "backgroundColor": "rgba(29,78,216,0.08)",
                    "fill": True,
                    "tension": 0.3,
                    "pointRadius": 0,
                    "borderWidth": 2,
                }
            ],
        },
        "options": {
            "responsive": True,
            "maintainAspectRatio": False,
            "plugins": {"legend": {"display": False}},
            "scales": {
                "y": {"ticks": {"font": {"size": 10}}},
                "x": {"ticks": {"font": {"size": 9}}},
            },
        },
    }

    _dscr_values = [min(x["dscr"], 10) for x in d["dscr"]]
    d["dscr_chart"] = {
        "type": "bar",
        "data": {
            "labels": [f"Y{x['year']}" for x in d["dscr"]],
            "datasets": [
                {
                    "data": _dscr_values,
                    "backgroundColor": [
                        "rgba(22,163,74,0.7)" if v >= 1.2 else "rgba(239,68,68,0.7)"
                        for v in _dscr_values
                    ],
                    "borderRadius": 4,
                }
            ],
        },
        "options": {
            "responsive": True,
            "maintainAspectRatio": False,
            "plugins": {"legend": {"display": False}},
            "scales": {
                "y": {"ticks": {"font": {"size": 10}}, "min": 0},
                "x": {"ticks": {"font": {"size": 10}}},
            },
        },
    }

    d["season_chart"] = {
        "type": "bar",
        "data": {
            "labels": [t[f"month_{k}"] for k in month_keys],
            "datasets": [
                {
                    "data": [v * 100 for v in s["season"]],
                    "backgroundColor": "rgba(29,78,216,0.6)",
                    "borderRadius": 3,
                }
            ],
        },
        "options": {
            "responsive": True,
            "maintainAspectRatio": False,
            "plugins": {"legend": {"display": False}},
            "scales": {
                "y": {"ticks": {"font": {"size": 10}}, "min": 0},
                "x": {"ticks": {"font": {"size": 10}}},
            },
        },
    }

    # Sensitivity tables (pre-computed for returns tab)
    sens_rows, price_rows = compute_sensitivity(s, d)
    d["sens_rows"] = sens_rows
    d["price_rows"] = price_rows


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


@bp.route("/")
async def index():
    scenario_count = 0
    default = None
    if g.user:
        scenario_count = await count_scenarios(g.user["id"])
        # Load specific scenario if ?scenario=<id> is set, else default
        scenario_id = request.args.get("scenario", type=int)
        if scenario_id:
            default = await fetch_one(
                "SELECT * FROM scenarios WHERE id = ? AND user_id = ? AND deleted_at IS NULL",
                (scenario_id, g.user["id"]),
            )
        if not default:
            default = await get_default_scenario(g.user["id"])
    initial_state = json.loads(default["state_json"]) if default else {}
    s = validate_state(initial_state)
    lang = g.get("lang", "en")
    d = calc(s, lang=lang)
    augment_d(d, s, lang)
    cur = COUNTRY_CURRENCY.get(s["country"], CURRENCY_DEFAULT)
    g.currency_sym = cur["sym"]
    g.currency_eu_style = cur["eu_style"]
    return await render_template(
        "planner.html",
        s=s,
        d=d,
        scenario_count=scenario_count,
        lang=lang,
        active_tab="capex",
        country_presets=COUNTRY_PRESETS,
        currency_sym=cur["sym"],
        step=1,
        total_steps=TOTAL_WIZARD_STEPS,
    )


@bp.route("/calculate", methods=["POST"])
async def calculate():
    """HTMX endpoint: form data → HTML tab partial + OOB swaps."""
    form = await request.form
    s = validate_state(form_to_state(form))
    lang = g.get("lang", "en")
    d = calc(s, lang=lang)
    augment_d(d, s, lang)
    active_tab = form.get("activeTab", "capex")
    if active_tab not in {"capex", "operating", "cashflow", "returns"}:
        active_tab = "capex"
    cur = COUNTRY_CURRENCY.get(s["country"], CURRENCY_DEFAULT)
    g.currency_sym = cur["sym"]
    g.currency_eu_style = cur["eu_style"]
    return await render_template(
        "partials/calculate_response.html",
        s=s,
        d=d,
        active_tab=active_tab,
        lang=lang,
        currency_sym=cur["sym"],
    )


TOTAL_WIZARD_STEPS = 4


@bp.route("/scenarios", methods=["GET"])
@login_required
async def scenario_list():
    scenarios = await get_scenarios(g.user["id"])
    return await render_template("partials/scenario_list.html", scenarios=scenarios)


@bp.route("/scenarios/save", methods=["POST"])
@login_required
@csrf_protect
async def save_scenario():
    form = await request.form
    name = form.get("scenario_name", "Untitled Scenario")
    # Build state_json from form data (same form as the planner)
    state_json = json.dumps(form_to_state(form))
    location = form.get("location", "")
    scenario_id = form.get("scenario_id")

    now = utcnow_iso()

    is_first_save = not scenario_id and (await count_scenarios(g.user["id"])) == 0

    if scenario_id:
        await execute(
            "UPDATE scenarios SET name = ?, state_json = ?, location = ?, updated_at = ? WHERE id = ? AND user_id = ? AND deleted_at IS NULL",
            (name, state_json, location, now, int(scenario_id), g.user["id"]),
        )
    else:
        scenario_id = await execute(
            "INSERT INTO scenarios (user_id, name, state_json, location, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
            (g.user["id"], name, state_json, location, now, now),
        )

    # Add to Resend nurture audience on first scenario save
    if is_first_save:
        from ..core import config as _config

        if _config.RESEND_AUDIENCE_PLANNER and _config.RESEND_API_KEY:
            try:
                import resend

                resend.api_key = _config.RESEND_API_KEY
                resend.Contacts.create(
                    {
                        "audience_id": _config.RESEND_AUDIENCE_PLANNER,
                        "email": g.user["email"],
                    }
                )
            except Exception as e:
                logger.warning("Failed to add %s to nurture audience: %s", g.user["email"], e)

    lang = g.get("lang", "en")
    t = get_translations(lang)
    return f'<div class="save-toast">&check; {t.get("scenario_saved", "Saved")}</div>'


@bp.route("/scenarios/<int:scenario_id>", methods=["GET"])
@login_required
async def get_scenario(scenario_id: int):
    row = await fetch_one(
        "SELECT * FROM scenarios WHERE id = ? AND user_id = ? AND deleted_at IS NULL",
        (scenario_id, g.user["id"]),
    )
    if not row:
        return jsonify({"error": "Not found"}), 404
    return jsonify(
        {
            "id": row["id"],
            "name": row["name"],
            "state_json": row["state_json"],
            "location": row["location"],
        }
    )


@bp.route("/scenarios/<int:scenario_id>", methods=["DELETE"])
@login_required
@csrf_protect
async def delete_scenario(scenario_id: int):
    now = utcnow_iso()
    await execute(
        "UPDATE scenarios SET deleted_at = ? WHERE id = ? AND user_id = ? AND deleted_at IS NULL",
        (now, scenario_id, g.user["id"]),
    )
    scenarios = await get_scenarios(g.user["id"])
    return await render_template("partials/scenario_list.html", scenarios=scenarios)


@bp.route("/scenarios/<int:scenario_id>/default", methods=["POST"])
@login_required
@csrf_protect
async def set_default(scenario_id: int):
    # Clear existing default
    await execute(
        "UPDATE scenarios SET is_default = 0 WHERE user_id = ?",
        (g.user["id"],),
    )
    # Set new default
    await execute(
        "UPDATE scenarios SET is_default = 1 WHERE id = ? AND user_id = ?",
        (scenario_id, g.user["id"]),
    )
    return jsonify({"ok": True})


# =============================================================================
# Business Plan PDF Export
# =============================================================================


@bp.route("/export")
@login_required
@feature_gate("planner_export", "export_waitlist.html")
async def export():
    """Export options page — language, scenario picker, pricing."""
    scenarios = await get_scenarios(g.user["id"])

    # Check for existing purchases
    exports = await fetch_all(
        "SELECT * FROM business_plan_exports WHERE user_id = ? ORDER BY created_at DESC",
        (g.user["id"],),
    )

    return await render_template(
        "export.html",
        scenarios=scenarios,
        exports=exports,
    )


@bp.route("/export/details", methods=["GET", "POST"])
@login_required
@csrf_protect
async def export_details():
    """Business plan details form — collect narrative fields before PDF export.

    GET: shows the form pre-populated from the scenario's bp_details_json.
    POST: saves bp_details_json to the scenario, redirects to /export/checkout.
    """
    from quart import redirect, url_for

    form = await request.form
    scenario_id = request.args.get("scenario_id") or form.get("scenario_id")
    language = request.args.get("language", "en") or form.get("language", "en")

    if not scenario_id:
        return redirect(url_for("planner.export"))

    scenario = await fetch_one(
        "SELECT id, name, bp_details_json FROM scenarios WHERE id = ? AND user_id = ? AND deleted_at IS NULL",
        (int(scenario_id), g.user["id"]),
    )
    if not scenario:
        return jsonify({"error": "Scenario not found."}), 404

    if request.method == "POST":
        _FIELDS = [
            "company_name", "legal_entity", "founder_name", "founder_background",
            "planned_location_address", "planned_opening_date", "facility_description",
            "marketing_concept", "operations_concept", "staffing_plan",
        ]
        details = {f: form.get(f, "").strip() for f in _FIELDS}
        await execute(
            "UPDATE scenarios SET bp_details_json = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?",
            (json.dumps(details), int(scenario_id), g.user["id"]),
        )
        return redirect(
            url_for("planner.export") + f"?scenario_id={scenario_id}&language={language}"
        )

    # GET — pre-populate from existing details
    existing = {}
    if scenario.get("bp_details_json"):
        try:
            existing = json.loads(scenario["bp_details_json"])
        except (json.JSONDecodeError, TypeError):
            existing = {}

    return await render_template(
        "export_details.html",
        scenario=scenario,
        scenario_id=scenario_id,
        language=language,
        details=existing,
    )


@bp.route("/export/checkout", methods=["POST"])
@login_required
@csrf_protect
async def export_checkout():
    """Return checkout JSON for business plan PDF (works with Paddle overlay or Stripe redirect)."""
    from ..billing.routes import _provider

    form = await request.form
    scenario_id = form.get("scenario_id")
    language = form.get("language", "en")

    if not scenario_id:
        return jsonify({"error": "Select a scenario."}), 400

    # Verify ownership
    scenario = await fetch_one(
        "SELECT id FROM scenarios WHERE id = ? AND user_id = ? AND deleted_at IS NULL",
        (int(scenario_id), g.user["id"]),
    )
    if not scenario:
        return jsonify({"error": "Scenario not found."}), 404

    price_id = await get_price_id("business_plan")
    if not price_id:
        return jsonify({"error": "Product not configured. Contact support."}), 500

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


@bp.route("/export/success")
@login_required
async def export_success():
    """Post-checkout landing — shows download link when ready."""
    exports = await fetch_all(
        "SELECT * FROM business_plan_exports WHERE user_id = ? ORDER BY created_at DESC LIMIT 1",
        (g.user["id"],),
    )
    return await render_template("export_success.html", exports=exports)


@bp.route("/export/<token>")
@login_required
async def export_download(token: str):
    """Download a generated PDF."""
    export = await fetch_one(
        "SELECT * FROM business_plan_exports WHERE token = ? AND user_id = ?",
        (token, g.user["id"]),
    )
    if not export:
        return jsonify({"error": "Export not found."}), 404

    if export["status"] == "pending" or export["status"] == "generating":
        return await render_template("export_generating.html", export=export)

    if export["status"] == "failed":
        return jsonify({"error": "PDF generation failed. Please contact support."}), 500

    # Serve the PDF file
    from pathlib import Path

    file_path = Path(export["file_path"])
    if not file_path.exists():
        return jsonify({"error": "PDF file not found."}), 404

    pdf_bytes = file_path.read_bytes()
    return Response(
        pdf_bytes,
        mimetype="application/pdf",
        headers={
            "Content-Disposition": f'attachment; filename="padel-business-plan-{export["id"]}.pdf"'
        },
    )


# =============================================================================
# DuckDB analytics integration — market data for planner pre-fill
# =============================================================================


@bp.route("/api/market-data")
async def market_data():
    """Return per-city planner defaults from DuckDB serving layer.

    GET /planner/api/market-data?city_slug=berlin

    Returns a partial DEFAULTS override dict (camelCase keys).
    Returns {} when the analytics DB has no data yet — caller merges with DEFAULTS.
    """
    city_slug = request.args.get("city_slug", "").strip()
    if not city_slug:
        return jsonify({}), 200

    from ..analytics import fetch_analytics

    rows = await fetch_analytics(
        "SELECT * FROM serving.planner_defaults WHERE city_slug = ? LIMIT 1",
        [city_slug],
    )
    if not rows:
        return jsonify({}), 200

    row = rows[0]

    # Map DuckDB snake_case columns → DEFAULTS camelCase keys.
    # Only include fields that exist in the row and have non-null values.
    col_map: dict[str, str] = {
        "rate_peak": "ratePeak",
        "rate_off_peak": "rateOffPeak",
        "avg_utilisation_pct": "utilTarget",
        "courts_typical": "dblCourts",
    }

    overrides: dict = {}
    for col, key in col_map.items():
        val = row.get(col)
        if val is not None:
            overrides[key] = round(float(val), 2)

    # Include data quality metadata so frontend can show confidence indicator
    if row.get("data_confidence") is not None:
        overrides["_dataConfidence"] = round(float(row["data_confidence"]), 2)
    if row.get("data_source"):
        overrides["_dataSource"] = row["data_source"]
    if row.get("country_code"):
        overrides["_countryCode"] = row["country_code"]
    if row.get("price_currency"):
        overrides["_currency"] = row["price_currency"]

    return jsonify(overrides), 200
