"""
Business Plan PDF generation engine.

Renders an HTML template with planner data, converts to PDF via WeasyPrint.
"""

import io
import json
from pathlib import Path

from .analytics import fetch_analytics
from .core import fetch_one
from .i18n import get_translations
from .planner.calculator import COUNTRY_CURRENCY, CURRENCY_DEFAULT, calc, validate_state
from .planner.routes import compute_sensitivity

TEMPLATE_DIR = Path(__file__).parent / "templates" / "businessplan"


def _fmt_cur(n, sym: str = "\u20ac", eu_style: bool = True) -> str:
    """Format number as currency with locale-aware thousands separator."""
    if n is None:
        return "-"
    v = round(float(n))
    s = f"{abs(v):,}"
    if eu_style:
        s = s.replace(",", ".")
    return f"-{sym}{s}" if v < 0 else f"{sym}{s}"


def _fmt_pct(n) -> str:
    """Format decimal as percentage."""
    if n is None:
        return "-"
    return f"{n * 100:.1f}%"


def _fmt_months(idx: int, t: dict) -> str:
    """Format payback month index as readable string."""
    if idx < 0:
        return t["bp_payback_not_reached"]
    months = idx + 1
    if months <= 12:
        return t["bp_months"].format(n=months)
    years = months / 12
    return t["bp_years"].format(n=f"{years:.1f}")


def _generate_pnl_chart_svg(annuals: list[dict], sym: str) -> str:
    """Generate P&L trend chart as SVG string using matplotlib.

    Returns an SVG string for embedding in WeasyPrint HTML.
    Returns empty string if matplotlib is unavailable.
    """
    try:
        import matplotlib
        matplotlib.use("Agg")
        import matplotlib.pyplot as plt
        import matplotlib.ticker as mticker
    except ImportError:
        return ""

    years = [f"Y{a['year']}" for a in annuals]
    revenues = [a["revenue"] / 1000 for a in annuals]
    ebitdas = [a["ebitda"] / 1000 for a in annuals]
    net_cfs = [a["ncf"] / 1000 for a in annuals]

    fig, ax = plt.subplots(figsize=(5.5, 2.8))
    x = range(len(years))
    width = 0.28

    ax.bar([i - width for i in x], revenues, width, label=f"Revenue ({sym}k)", color="#1D4ED8", alpha=0.85)
    ax.bar([i for i in x], ebitdas, width, label=f"EBITDA ({sym}k)", color="#16A34A", alpha=0.85)
    ax.bar([i + width for i in x], net_cfs, width, label=f"Net CF ({sym}k)", color="#D97706", alpha=0.85)

    ax.set_xticks(list(x))
    ax.set_xticklabels(years, fontsize=8)
    ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda v, _: f"{sym}{v:.0f}k"))
    ax.tick_params(axis="y", labelsize=7)
    ax.legend(fontsize=7, loc="upper left", framealpha=0.5)
    ax.spines["top"].set_visible(False)
    ax.spines["right"].set_visible(False)
    ax.grid(axis="y", linestyle="--", alpha=0.4)
    ax.set_facecolor("#FAFAFA")
    fig.patch.set_facecolor("white")
    fig.tight_layout(pad=0.5)

    buf = io.BytesIO()
    fig.savefig(buf, format="svg", dpi=150, bbox_inches="tight")
    plt.close(fig)
    buf.seek(0)
    return buf.read().decode("utf-8")


def _generate_cashflow_chart_svg(months: list[dict], sym: str) -> str:
    """Generate 12-month cumulative cash flow chart as SVG string using matplotlib.

    Returns an SVG string for embedding in WeasyPrint HTML.
    Returns empty string if matplotlib is unavailable.
    """
    try:
        import matplotlib
        matplotlib.use("Agg")
        import matplotlib.pyplot as plt
        import matplotlib.ticker as mticker
    except ImportError:
        return ""

    m12 = months[:12]
    labels = [str(m["m"]) for m in m12]
    cumulatives = [m["cum"] / 1000 for m in m12]
    ncfs = [m["ncf"] / 1000 for m in m12]

    fig, ax = plt.subplots(figsize=(5.5, 2.6))
    ax.bar(range(len(labels)), ncfs, color=["#16A34A" if v >= 0 else "#EF4444" for v in ncfs], alpha=0.7, label=f"Monthly NCF ({sym}k)")
    ax.plot(range(len(labels)), cumulatives, color="#1D4ED8", linewidth=2, marker="o", markersize=4, label=f"Cumulative ({sym}k)")
    ax.axhline(0, color="#94A3B8", linewidth=0.8, linestyle="--")

    ax.set_xticks(range(len(labels)))
    ax.set_xticklabels([f"M{lbl}" for lbl in labels], fontsize=7)
    ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda v, _: f"{sym}{v:.0f}k"))
    ax.tick_params(axis="y", labelsize=7)
    ax.legend(fontsize=7, loc="lower right", framealpha=0.5)
    ax.spines["top"].set_visible(False)
    ax.spines["right"].set_visible(False)
    ax.grid(axis="y", linestyle="--", alpha=0.4)
    ax.set_facecolor("#FAFAFA")
    fig.patch.set_facecolor("white")
    fig.tight_layout(pad=0.5)

    buf = io.BytesIO()
    fig.savefig(buf, format="svg", dpi=150, bbox_inches="tight")
    plt.close(fig)
    buf.seek(0)
    return buf.read().decode("utf-8")


def _compute_opening_balance(d: dict, fmt) -> dict:
    """Compute the opening balance sheet (Eröffnungsbilanz) from CAPEX + financing data."""
    # Assets side: CAPEX items + working capital buffer
    fixed_assets = sum(
        i["amount"] for i in d["capexItems"]
        if not any(kw in i.get("name", "").lower() for kw in ["working", "betrieb", "reserve", "puffer"])
    )
    working_capital = d["capex"] - fixed_assets

    # Liabilities: loan; Equity: everything else
    total_assets = d["capex"]
    loan = d["loanAmount"]
    equity = d["equity"]

    return {
        "total_assets": fmt(total_assets),
        "fixed_assets": fmt(fixed_assets),
        "working_capital": fmt(max(working_capital, 0)),
        "total_liabilities_equity": fmt(total_assets),
        "loan": fmt(loan),
        "equity": fmt(equity),
        "equity_ratio": f"{(equity / total_assets * 100):.1f}%" if total_assets > 0 else "-",
    }


def _compute_use_of_funds(d: dict, fmt) -> list[dict]:
    """Map loan to CAPEX line items (Mittelverwendungsplan)."""
    loan = d["loanAmount"]
    total = d["capex"]

    rows = []
    for item in d["capexItems"]:
        if item["amount"] <= 0:
            continue
        share = item["amount"] / total if total > 0 else 0
        from_loan = min(item["amount"], loan * share * 1.1)  # approx allocation
        from_equity = item["amount"] - from_loan
        rows.append({
            "item": item["name"],
            "total": fmt(item["amount"]),
            "from_loan": fmt(round(from_loan)),
            "from_equity": fmt(round(from_equity)),
        })
    return rows


async def _fetch_market_data(location: str, language: str) -> dict | None:
    """Query DuckDB for city-level market data to auto-populate market analysis.

    Returns None if no matching city is found.
    """
    if not location:
        return None

    # Try matching city slug (lowercase, spaces→hyphens)
    city_slug = location.lower().strip().replace(" ", "-").replace(",", "")

    rows = await fetch_analytics(
        """
        SELECT city_slug, city_name, country_name_en AS country,
               city_padel_venue_count AS padel_venue_count,
               city_venues_per_100k AS venues_per_100k,
               opportunity_score,
               median_peak_rate, median_offpeak_rate, median_occupancy_rate,
               population
        FROM serving.location_profiles
        WHERE city_slug IS NOT NULL
          AND (city_slug = ? OR lower(city_name) = lower(?))
        ORDER BY opportunity_score DESC
        LIMIT 1
        """,
        [city_slug, location.strip()],
    )
    if not rows:
        return None
    return rows[0]


def get_plan_sections(state: dict, d: dict, language: str = "en", bp_details: dict | None = None, market_data: dict | None = None) -> dict:
    """Extract and format all business plan sections from planner data."""
    s = state
    t = get_translations(language)

    cur = COUNTRY_CURRENCY.get(s.get("country", "DE"), CURRENCY_DEFAULT)
    sym, eu_style = cur["sym"], cur["eu_style"]
    fmt = lambda n: _fmt_cur(n, sym, eu_style)  # noqa: E731

    venue_type = t["bp_indoor"] if s["venue"] == "indoor" else t["bp_outdoor"]
    own_type = t["bp_own"] if s["own"] == "buy" else t["bp_rent"]

    payback_str = _fmt_months(d["paybackIdx"], t)
    irr_str = _fmt_pct(d["irr"])
    total_capex_str = fmt(d["capex"])
    equity_str = fmt(d["equity"])
    loan_str = fmt(d["loanAmount"])
    per_court_str = fmt(d["capexPerCourt"])
    per_sqm_str = fmt(d["capexPerSqm"])

    details = bp_details or {}
    sens_rows, price_rows = compute_sensitivity(s, d)

    # Format sensitivity rows for display
    def fmt_sens(rows):
        return [{**r, "rev_fmt": fmt(r["rev"]), "ncf_fmt": fmt(r["ncf"]), "annual_fmt": fmt(r["annual"]), "dscr_fmt": f"{r['dscr']:.2f}x"} for r in rows]

    def fmt_price(rows):
        return [{**r, "adj_rate_fmt": fmt(r["adj_rate"]), "rev_fmt": fmt(r["rev"]), "ncf_fmt": fmt(r["ncf"])} for r in rows]

    opening_balance = _compute_opening_balance(d, fmt)
    use_of_funds = _compute_use_of_funds(d, fmt)

    # Generate SVG charts from data
    pnl_chart_svg = _generate_pnl_chart_svg(d["annuals"], sym)
    cashflow_chart_svg = _generate_cashflow_chart_svg(d["months"], sym)

    sections = {
        "lang": language,
        "title": t["bp_title"],
        "subtitle": f"{venue_type} ({own_type}) \u2014 {s.get('country', 'DE')}",
        "courts": t["bp_courts_desc"].format(dbl=s["dblCourts"], sgl=s["sglCourts"], total=d["totalCourts"]),

        # Executive Summary
        "executive_summary": {
            "heading": t["bp_exec_summary"],
            "facility_type": f"{venue_type} ({own_type})",
            "courts": d["totalCourts"],
            "sqm": d["sqm"],
            "total_capex": total_capex_str,
            "equity": equity_str,
            "loan": loan_str,
            "y1_revenue": fmt(d["annuals"][0]["revenue"]) if d["annuals"] else "-",
            "y3_ebitda": fmt(d["stabEbitda"]),
            "irr": irr_str,
            "payback": payback_str,
        },

        # Investment Plan (CAPEX)
        "investment": {
            "heading": t["bp_investment"],
            "items": [{**i, "formatted_amount": fmt(i["amount"])} for i in d["capexItems"]],
            "total": total_capex_str,
            "per_court": per_court_str,
            "per_sqm": per_sqm_str,
        },

        # Operating Costs
        "operations": {
            "heading": t["bp_operations"],
            "items": [{**i, "formatted_amount": fmt(i["amount"])} for i in d["opexItems"]],
            "monthly_total": fmt(d["opex"]),
            "annual_total": fmt(d["annualOpex"]),
        },

        # Revenue Model
        "revenue": {
            "heading": t["bp_revenue"],
            "weighted_rate": fmt(d["weightedRate"]),
            "utilization": _fmt_pct(s["utilTarget"] / 100),
            "gross_monthly": fmt(d["grossRevMonth"]),
            "net_monthly": fmt(d["netRevMonth"]),
            "ebitda_monthly": fmt(d["ebitdaMonth"]),
            "net_cf_monthly": fmt(d["netCFMonth"]),
        },

        # 5-Year P&L
        "annuals": {
            "heading": t["bp_annuals"],
            "years": [
                {
                    "year": a["year"],
                    "revenue": fmt(a["revenue"]),
                    "ebitda": fmt(a["ebitda"]),
                    "debt_service": fmt(a["ds"]),
                    "net_cf": fmt(a["ncf"]),
                }
                for a in d["annuals"]
            ],
        },

        # Financing
        "financing": {
            "heading": t["bp_financing"],
            "loan_pct": _fmt_pct(s["loanPct"] / 100),
            "equity": equity_str,
            "loan": loan_str,
            "interest_rate": f"{s['interestRate']}%",
            "term": t["bp_years"].format(n=s["loanTerm"]),
            "monthly_payment": fmt(d["monthlyPayment"]),
            "annual_debt_service": fmt(d["annualDebtService"]),
            "ltv": _fmt_pct(d["ltv"]),
        },

        # Key Metrics
        "metrics": {
            "heading": t["bp_metrics"],
            "irr": irr_str,
            "moic": f"{d['moic']:.2f}x",
            "cash_on_cash": _fmt_pct(d["cashOnCash"]),
            "payback": payback_str,
            "break_even_util": _fmt_pct(d["breakEvenUtil"]),
            "ebitda_margin": _fmt_pct(d["ebitdaMargin"]),
            "dscr_y3": f"{d['dscr'][2]['dscr']:.2f}x" if len(d["dscr"]) >= 3 else "-",
            "yield_on_cost": _fmt_pct(d["yieldOnCost"]),
        },

        # 12-Month Cash Flow
        "cashflow_12m": {
            "heading": t["bp_cashflow_12m"],
            "months": [
                {
                    "month": m["m"],
                    "revenue": fmt(m["totalRev"]),
                    "opex": fmt(abs(m["opex"])),
                    "ebitda": fmt(m["ebitda"]),
                    "debt": fmt(abs(m["loan"])),
                    "ncf": fmt(m["ncf"]),
                    "cumulative": fmt(m["cum"]),
                }
                for m in d["months"][:12]
            ],
        },

        # Charts (SVG strings, embedded directly in HTML)
        "charts": {
            "pnl": pnl_chart_svg,
            "cashflow": cashflow_chart_svg,
        },

        # Sensitivity analysis
        "sensitivity": {
            "heading": t["bp_sensitivity"],
            "util_heading": t["bp_sensitivity_util"],
            "price_heading": t["bp_sensitivity_price"],
            "sens_rows": fmt_sens(sens_rows),
            "price_rows": fmt_price(price_rows),
        },

        # Opening balance sheet (Eröffnungsbilanz)
        "opening_balance": {
            "heading": t["bp_opening_balance"],
            **opening_balance,
        },

        # Use of funds (Mittelverwendungsplan)
        "use_of_funds": {
            "heading": t["bp_use_of_funds"],
            "rows": use_of_funds,
            "total_loan": fmt(d["loanAmount"]),
            "total_equity": fmt(d["equity"]),
            "total_capex": fmt(d["capex"]),
        },

        # Narrative sections from bp_details form
        "narrative": {
            "company_name": details.get("company_name", ""),
            "legal_entity": details.get("legal_entity", ""),
            "founder_name": details.get("founder_name", ""),
            "founder_background": details.get("founder_background", ""),
            "planned_location_address": details.get("planned_location_address", ""),
            "planned_opening_date": details.get("planned_opening_date", ""),
            "facility_description": details.get("facility_description", ""),
            "marketing_concept": details.get("marketing_concept", ""),
            "operations_concept": details.get("operations_concept", ""),
            "staffing_plan": details.get("staffing_plan", ""),
        },

        # Market data (auto-populated from DuckDB, or None)
        "market_data": market_data,

        # Template labels
        "labels": {
            "scenario": t["bp_lbl_scenario"],
            "generated_by": t["bp_lbl_generated_by"],
            "exec_paragraph": t["bp_exec_paragraph"].format(
                facility_type=f"{venue_type} ({own_type})",
                courts=d["totalCourts"],
                sqm=d["sqm"],
                total_capex=total_capex_str,
                equity=equity_str,
                loan=loan_str,
                irr=irr_str,
                payback=payback_str,
            ),
            "total_investment": t["bp_lbl_total_investment"],
            "equity_required": t["bp_lbl_equity_required"],
            "year3_ebitda": t["bp_lbl_year3_ebitda"],
            "irr": t["bp_lbl_irr"],
            "payback_period": t["bp_lbl_payback_period"],
            "year1_revenue": t["bp_lbl_year1_revenue"],
            "item": t["bp_lbl_item"],
            "amount": t["bp_lbl_amount"],
            "notes": t["bp_lbl_notes"],
            "total_capex": t["bp_lbl_total_capex"],
            "capex_stats": t["bp_lbl_capex_stats"].format(per_court=per_court_str, per_sqm=per_sqm_str),
            "equity": t["bp_lbl_equity"],
            "loan": t["bp_lbl_loan"],
            "interest_rate": t["bp_lbl_interest_rate"],
            "loan_term": t["bp_lbl_loan_term"],
            "monthly_payment": t["bp_lbl_monthly_payment"],
            "annual_debt_service": t["bp_lbl_annual_debt_service"],
            "ltv": t["bp_lbl_ltv"],
            "monthly": t["bp_lbl_monthly"],
            "total_monthly_opex": t["bp_lbl_total_monthly_opex"],
            "annual_opex": t["bp_lbl_annual_opex"],
            "weighted_hourly_rate": t["bp_lbl_weighted_hourly_rate"],
            "target_utilization": t["bp_lbl_target_utilization"],
            "gross_monthly_revenue": t["bp_lbl_gross_monthly_revenue"],
            "net_monthly_revenue": t["bp_lbl_net_monthly_revenue"],
            "monthly_ebitda": t["bp_lbl_monthly_ebitda"],
            "monthly_net_cf": t["bp_lbl_monthly_net_cf"],
            "year": t["bp_lbl_year"],
            "revenue": t["bp_lbl_revenue"],
            "ebitda": t["bp_lbl_ebitda"],
            "debt_service": t["bp_lbl_debt_service"],
            "net_cf": t["bp_lbl_net_cf"],
            "moic": t["bp_lbl_moic"],
            "cash_on_cash": t["bp_lbl_cash_on_cash"],
            "payback": t["bp_lbl_payback"],
            "break_even_util": t["bp_lbl_break_even_util"],
            "ebitda_margin": t["bp_lbl_ebitda_margin"],
            "dscr_y3": t["bp_lbl_dscr_y3"],
            "yield_on_cost": t["bp_lbl_yield_on_cost"],
            "month": t["bp_lbl_month"],
            "opex": t["bp_lbl_opex"],
            "debt": t["bp_lbl_debt"],
            "cumulative": t["bp_lbl_cumulative"],
            "disclaimer": t["bp_lbl_disclaimer"],
            "currency_sym": sym,
            # New section headings
            "sensitivity": t["bp_sensitivity"],
            "sensitivity_util": t["bp_sensitivity_util"],
            "sensitivity_price": t["bp_sensitivity_price"],
            "opening_balance": t["bp_opening_balance"],
            "use_of_funds": t["bp_use_of_funds"],
            "market_analysis": t["bp_market_analysis"],
            "risk_analysis": t["bp_risk_analysis"],
            "venture_description": t["bp_venture_description"],
            "founder_profile": t["bp_founder_profile"],
            # New table labels
            "utilization": t["bp_lbl_utilization"],
            "annual_ncf": t["bp_lbl_annual_ncf"],
            "dscr": t["bp_lbl_dscr"],
            "price_delta": t["bp_lbl_price_delta"],
            "hourly_rate": t["bp_lbl_hourly_rate"],
            "monthly_ncf": t["bp_lbl_monthly_ncf"],
            "assets": t["bp_lbl_assets"],
            "fixed_assets": t["bp_lbl_fixed_assets"],
            "working_capital": t["bp_lbl_working_capital"],
            "liabilities_equity": t["bp_lbl_liabilities_equity"],
            "equity_ratio": t["bp_lbl_equity_ratio"],
            "funded_by_loan": t["bp_lbl_funded_by_loan"],
            "funded_by_equity": t["bp_lbl_funded_by_equity"],
            "total": t["bp_lbl_total"],
            "venues_per_100k": t["bp_lbl_venues_per_100k"],
            "padelnomics_score": t["bp_lbl_padelnomics_score"],
            "median_peak_rate": t["bp_lbl_median_peak_rate"],
            "median_offpeak_rate": t["bp_lbl_median_offpeak_rate"],
            "median_occupancy": t["bp_lbl_median_occupancy"],
            "confidential": t["bp_lbl_confidential"],
            "table_of_contents": t["bp_lbl_table_of_contents"],
        },
    }

    return sections


async def generate_business_plan(scenario_id: int, user_id: int, language: str = "en") -> bytes:
    """Generate a business plan PDF from a saved scenario. Returns PDF bytes."""
    scenario = await fetch_one(
        "SELECT * FROM scenarios WHERE id = ? AND user_id = ?",
        (scenario_id, user_id),
    )
    if not scenario:
        raise ValueError(f"Scenario {scenario_id} not found for user {user_id}")

    state = validate_state(json.loads(scenario["state_json"]))
    d = calc(state)

    # Load optional bp_details from scenario
    bp_details = None
    if scenario.get("bp_details_json"):
        try:
            bp_details = json.loads(scenario["bp_details_json"])
        except (json.JSONDecodeError, TypeError):
            bp_details = None

    # Attempt to fetch market data from DuckDB
    location = scenario.get("location", "") or ""
    market_data = await _fetch_market_data(location, language)

    sections = get_plan_sections(state, d, language, bp_details=bp_details, market_data=market_data)
    sections["scenario_name"] = scenario["name"]
    sections["location"] = location

    # Read HTML + CSS template
    html_template = (TEMPLATE_DIR / "plan.html").read_text()
    css = (TEMPLATE_DIR / "plan.css").read_text()

    # Render with Jinja
    from jinja2 import Template
    template = Template(html_template)
    rendered_html = template.render(s=sections, css=css)

    # Convert to PDF via WeasyPrint
    from weasyprint import HTML
    pdf_bytes = HTML(string=rendered_html).write_pdf()

    return pdf_bytes
