"""
Tests for the business plan PDF export pipeline.

Covers:
  - get_plan_sections() — pure formatting function
  - generate_business_plan() — end-to-end WeasyPrint rendering
  - handle_generate_business_plan() — worker handler status transitions
  - /planner/export and /planner/export/<id> routes
"""
from datetime import UTC, datetime
from pathlib import Path

import pytest
from padelnomics.businessplan import generate_business_plan, get_plan_sections
from padelnomics.planner.calculator import calc, validate_state

try:
    from weasyprint import HTML as _WP  # noqa: F401
    _HAS_WEASYPRINT = True
except OSError:
    _HAS_WEASYPRINT = False

requires_weasyprint = pytest.mark.skipif(
    not _HAS_WEASYPRINT, reason="WeasyPrint native libs not available"
)


# ── Helpers ──────────────────────────────────────────────────────────────────

EXPECTED_SECTION_KEYS = {
    "lang",
    "title",
    "subtitle",
    "courts",
    "executive_summary",
    "investment",
    "operations",
    "revenue",
    "annuals",
    "financing",
    "metrics",
    "cashflow_12m",
    "labels",
}


def _make_sections(language: str = "en", country: str = "DE") -> dict:
    state = validate_state({"dblCourts": 4, "sglCourts": 2, "country": country})
    d = calc(state)
    return get_plan_sections(state, d, language)


# ════════════════════════════════════════════════════════════
# get_plan_sections() — pure function, no DB, no WeasyPrint
# ════════════════════════════════════════════════════════════

class TestGetPlanSections:
    def test_returns_all_sections_en(self):
        sections = _make_sections("en")
        assert EXPECTED_SECTION_KEYS.issubset(sections.keys())

    def test_returns_all_sections_de(self):
        sections = _make_sections("de")
        assert EXPECTED_SECTION_KEYS.issubset(sections.keys())

    def test_lang_field_matches_requested_language(self):
        assert _make_sections("en")["lang"] == "en"
        assert _make_sections("de")["lang"] == "de"

    def test_currency_formatting_eu_style(self):
        """German/EU style: € prefix, dot as thousands separator."""
        sections = _make_sections("de", country="DE")
        capex = sections["investment"]["total"]
        # EU-style: €1.234.567 (dot separator, no comma)
        assert capex.startswith("€")
        assert "," not in capex  # no comma separator in EU style

    def test_currency_formatting_us_style(self):
        """US style: $ prefix, comma as thousands separator."""
        sections = _make_sections("en", country="US")
        capex = sections["investment"]["total"]
        assert capex.startswith("$")
        assert "." not in capex.replace(".", "")  # commas, not dots

    def test_payback_formatting_en(self):
        sections = _make_sections("en")
        payback = sections["executive_summary"]["payback"]
        # Should be "X months" or "X.X years" or "not reached"
        assert isinstance(payback, str)
        assert len(payback) > 0

    def test_irr_formatting(self):
        sections = _make_sections("en")
        irr = sections["metrics"]["irr"]
        assert "%" in irr or irr == "-"

    def test_executive_summary_has_required_keys(self):
        sections = _make_sections("en")
        es = sections["executive_summary"]
        for key in ("heading", "courts", "sqm", "total_capex", "equity", "loan",
                    "y1_revenue", "y3_ebitda", "irr", "payback"):
            assert key in es, f"Missing key in executive_summary: {key}"

    def test_labels_has_required_keys(self):
        sections = _make_sections("en")
        labels = sections["labels"]
        for key in ("scenario", "generated_by", "disclaimer", "currency_sym",
                    "total_investment", "equity_required", "year3_ebitda"):
            assert key in labels, f"Missing key in labels: {key}"

    def test_annuals_has_5_years(self):
        sections = _make_sections("en")
        years = sections["annuals"]["years"]
        assert len(years) == 5

    def test_cashflow_12m_has_12_months(self):
        sections = _make_sections("en")
        months = sections["cashflow_12m"]["months"]
        assert len(months) == 12

    def test_investment_items_have_formatted_amounts(self):
        sections = _make_sections("en")
        for item in sections["investment"]["items"]:
            assert "formatted_amount" in item

    def test_de_labels_are_german(self):
        de_sections = _make_sections("de")
        en_sections = _make_sections("en")
        # Title should differ between languages
        assert de_sections["title"] != en_sections["title"]


# ════════════════════════════════════════════════════════════
# generate_business_plan() — end-to-end WeasyPrint
# ════════════════════════════════════════════════════════════

@requires_weasyprint
class TestGenerateBusinessPlan:
    async def test_generates_valid_pdf_en(self, db, scenario):
        pdf_bytes = await generate_business_plan(
            scenario["id"], scenario["user_id"], "en"
        )
        assert isinstance(pdf_bytes, bytes)
        assert pdf_bytes[:4] == b"%PDF"
        assert len(pdf_bytes) > 10_000  # non-trivially sized

    async def test_generates_valid_pdf_de(self, db, scenario):
        pdf_bytes = await generate_business_plan(
            scenario["id"], scenario["user_id"], "de"
        )
        assert isinstance(pdf_bytes, bytes)
        assert pdf_bytes[:4] == b"%PDF"
        assert len(pdf_bytes) > 10_000

    async def test_raises_for_missing_scenario(self, db, test_user):
        with pytest.raises(ValueError, match="not found"):
            await generate_business_plan(99999, test_user["id"], "en")

    async def test_raises_for_wrong_user(self, db, scenario):
        with pytest.raises(ValueError, match="not found"):
            await generate_business_plan(scenario["id"], scenario["user_id"] + 999, "en")


# ════════════════════════════════════════════════════════════
# Worker handler — status transitions + file write
# ════════════════════════════════════════════════════════════

async def _insert_export(db, user_id: int, scenario_id: int, status: str = "pending") -> dict:
    """Insert a business_plan_exports row, return dict with id and token."""
    import secrets
    now = datetime.now(UTC).isoformat()
    token = secrets.token_urlsafe(16)
    async with db.execute(
        """INSERT INTO business_plan_exports
           (user_id, scenario_id, language, status, token, created_at)
           VALUES (?, ?, 'en', ?, ?, ?)""",
        (user_id, scenario_id, status, token, now),
    ) as cursor:
        export_id = cursor.lastrowid
    await db.commit()
    return {"id": export_id, "token": token}


@requires_weasyprint
class TestWorkerHandler:
    async def test_happy_path_generates_pdf_and_updates_status(self, db, scenario, mock_send_email):
        from padelnomics.worker import handle_generate_business_plan

        export = await _insert_export(db, scenario["user_id"], scenario["id"])
        output_file = None

        await handle_generate_business_plan({
            "export_id": export["id"],
            "user_id": scenario["user_id"],
            "scenario_id": scenario["id"],
            "language": "en",
        })

        # Status should be 'ready'
        from padelnomics.core import fetch_one
        row = await fetch_one(
            "SELECT * FROM business_plan_exports WHERE id = ?", (export["id"],)
        )
        try:
            assert row["status"] == "ready"
            assert row["file_path"] is not None
            assert row["completed_at"] is not None

            # File should exist on disk and be a valid PDF
            output_file = Path(row["file_path"])
            assert output_file.exists()
            assert output_file.read_bytes()[:4] == b"%PDF"

            # Email should have been sent
            mock_send_email.assert_called_once()
            assert "to" in mock_send_email.call_args.kwargs
            assert "subject" in mock_send_email.call_args.kwargs
        finally:
            if output_file and output_file.exists():
                output_file.unlink()

    async def test_marks_failed_on_bad_scenario(self, db, scenario, mock_send_email):
        """Handler marks export failed when user_id doesn't match scenario owner."""
        from padelnomics.worker import handle_generate_business_plan

        # Use a non-existent user_id so generate_business_plan raises ValueError
        wrong_user_id = scenario["user_id"] + 9999
        export = await _insert_export(db, scenario["user_id"], scenario["id"])

        with pytest.raises(ValueError):
            await handle_generate_business_plan({
                "export_id": export["id"],
                "user_id": wrong_user_id,
                "scenario_id": scenario["id"],
                "language": "en",
            })

        from padelnomics.core import fetch_one
        row = await fetch_one(
            "SELECT status FROM business_plan_exports WHERE id = ?", (export["id"],)
        )
        assert row["status"] == "failed"


# ════════════════════════════════════════════════════════════
# Export routes — thin HTTP layer
# ════════════════════════════════════════════════════════════

class TestExportRoutes:
    async def test_export_page_requires_login(self, client):
        resp = await client.get("/en/planner/export")
        assert resp.status_code == 302

    async def test_export_page_shows_scenarios(self, auth_client, db, scenario):
        await db.execute(
            "INSERT OR REPLACE INTO feature_flags (name, enabled, description) VALUES ('planner_export', 1, '')"
        )
        await db.commit()
        resp = await auth_client.get("/en/planner/export")
        assert resp.status_code == 200
        html = (await resp.data).decode()
        assert "Test Scenario" in html

    async def test_download_nonexistent_returns_404(self, auth_client):
        resp = await auth_client.get("/en/planner/export/bogus-token-xyz")
        assert resp.status_code == 404

    async def test_download_pending_shows_generating_page(self, auth_client, db, scenario):
        export = await _insert_export(
            db, scenario["user_id"], scenario["id"], status="pending"
        )
        resp = await auth_client.get(f"/en/planner/export/{export['token']}")
        assert resp.status_code == 200
        await resp.data  # consume response
        # Should show the generating/pending template, not a PDF
        assert "pdf" not in resp.content_type.lower()

    async def test_download_generating_shows_generating_page(self, auth_client, db, scenario):
        export = await _insert_export(
            db, scenario["user_id"], scenario["id"], status="generating"
        )
        resp = await auth_client.get(f"/en/planner/export/{export['token']}")
        assert resp.status_code == 200
        await resp.data  # consume response
        assert "pdf" not in resp.content_type.lower()

    async def test_download_ready_returns_pdf(self, auth_client, db, scenario, tmp_path):
        # Write a minimal valid PDF to a temp file
        dummy_pdf = b"%PDF-1.4 tiny test pdf"
        pdf_file = tmp_path / "test_export.pdf"
        pdf_file.write_bytes(dummy_pdf)

        import secrets
        now = datetime.now(UTC).isoformat()
        token = secrets.token_urlsafe(16)
        async with db.execute(
            """INSERT INTO business_plan_exports
               (user_id, scenario_id, language, status, token, file_path, created_at, completed_at)
               VALUES (?, ?, 'en', 'ready', ?, ?, ?, ?)""",
            (scenario["user_id"], scenario["id"], token, str(pdf_file), now, now),
        ):
            pass
        await db.commit()

        resp = await auth_client.get(f"/en/planner/export/{token}")
        assert resp.status_code == 200
        assert resp.content_type == "application/pdf"
        data = await resp.data
        assert data == dummy_pdf

    async def test_download_other_users_export_returns_404(self, app, db, scenario):
        """Another user cannot download someone else's export."""
        now = datetime.now(UTC).isoformat()
        # Create a second user
        async with db.execute(
            "INSERT INTO users (email, name, created_at) VALUES (?, ?, ?)",
            ("other@example.com", "Other", now),
        ) as cursor:
            other_id = cursor.lastrowid
        await db.commit()

        export = await _insert_export(db, scenario["user_id"], scenario["id"])

        # Log in as the other user
        async with app.test_client() as c:
            async with c.session_transaction() as sess:
                sess["user_id"] = other_id
            resp = await c.get(f"/en/planner/export/{export['token']}")
        assert resp.status_code == 404
