"""Production smoke tests — must pass after every merge to main.

Run after deploy completes (~2-3min after merge). CI workflow:
  cd backend && uv run pytest tests/smoke/test_prod_smoke.py -m prod_smoke -v

Each test makes a real HTTP call to https://gendsgn.ru, validates response
structure (not exact data) и crash regressions, and reports timing.

Marks:
- @pytest.mark.prod_smoke — skip by default in CI, run only on-demand
- @pytest.mark.slow — long-running (≥5s)

Tests cover the regression surface that bot review window cannot catch:
- Schema migrations break field types → AttributeError на user actions
- Frontend bundle ↔ backend contract drift
- Auth/admin endpoints за токеном
- Background workers + Celery queue
"""

from __future__ import annotations

import os

import httpx
import pytest

BASE_URL = os.getenv("PROD_SMOKE_BASE_URL", "https://gendsgn.ru")
ADMIN_TOKEN = os.getenv("PROD_SMOKE_ADMIN_TOKEN")
TIMEOUT = httpx.Timeout(30.0, connect=10.0)

# Test cads — known-good в prod, в quarter 66:41:0303161 (Крауля)
TEST_CAD = "66:41:0204016:10"  # Билимбаевская, 32 ЖК velocity baseline
TEST_QUARTER = "66:41:0303161"  # 502 объекта (170 ЗУ + 256 зд + 75 соор + 1 ЕНК)


pytestmark = pytest.mark.prod_smoke


# ─────────────────────────────────────────────────────────────────────────────
# Public endpoints (no auth)
# ─────────────────────────────────────────────────────────────────────────────


def test_health_returns_ok() -> None:
    """Smoke: /health должен возвращать {"status":"ok"}."""
    r = httpx.get(f"{BASE_URL}/health", timeout=TIMEOUT)
    assert r.status_code == 200
    assert r.json().get("status") == "ok"


def test_site_finder_page_renders() -> None:
    """SSR returns HTML с заголовком GenDesign."""
    r = httpx.get(f"{BASE_URL}/site-finder", timeout=TIMEOUT)
    assert r.status_code == 200
    assert "GenDesign" in r.text or "Site Finder" in r.text


def test_parcels_analyze_returns_200() -> None:
    """REGRESSION SHIELD: ловит crashes в analyze pipeline.

    Раньше ломали:
    - PR #149: body stream double-read (api.ts)
    - PR #152: SQLAlchemy :weights::jsonb cast
    - PR #160: :window_interval::interval cast
    - PR #169 → 'int has no attribute strip' (cad_buildings.floors INT)

    Test: POST /api/v1/parcels/{cad}/analyze → 200 + required fields.
    """
    r = httpx.post(
        f"{BASE_URL}/api/v1/parcels/{TEST_CAD}/analyze",
        json={},
        timeout=httpx.Timeout(60.0, connect=10.0),  # analyze может быть медленным
    )
    assert r.status_code == 200, f"Expected 200, got {r.status_code}: {r.text[:500]}"
    body = r.json()
    # Required top-level keys (validate схема не дрейфит):
    required = ["cad_num", "score", "pipeline_24mo", "velocity", "gate_verdict"]
    for key in required:
        assert key in body, f"Missing field '{key}' in analyze response. Keys: {list(body.keys())}"
    # neighbors_summary must NOT crash even when floors=int (regression #169):
    assert "neighbors_summary" in body
    assert body["neighbors_summary"].get("data_available") is not False or (
        body["neighbors_summary"].get("note", "").startswith("neighbors query failed") is False
    )


def test_parcels_analyze_velocity_block_present() -> None:
    """REGRESSION SHIELD: velocity migrated to objective_corpus_room_month (#158, #163).

    После migration ожидаем что для testovogo cad есть velocity-блок:
    - score ≥ 0
    - period.start/end не None (значит objective fresh data есть)
    - by_room_bucket список (per-room breakdown — PR #163)
    """
    r = httpx.post(
        f"{BASE_URL}/api/v1/parcels/{TEST_CAD}/analyze",
        json={},
        timeout=httpx.Timeout(60.0, connect=10.0),
    )
    assert r.status_code == 200
    velocity = r.json().get("velocity")
    if velocity is None:
        pytest.skip("velocity null — possibly no data in cadastre, not regression")
    assert "score" in velocity
    assert "period" in velocity
    # PR #163 enrichment:
    assert "by_room_bucket" in velocity, "Missing by_room_bucket per PR #163"


def test_parcels_analyze_pipeline_by_class_enriched() -> None:
    """REGRESSION SHIELD: pipeline_by_class obj_class enrichment (#165).

    Раньше показывало "Не указан: 4075". После #165 — стандарт/комфорт/etc
    через JOIN на objective_complex_mapping.
    """
    r = httpx.post(
        f"{BASE_URL}/api/v1/parcels/{TEST_CAD}/analyze",
        json={},
        timeout=httpx.Timeout(60.0, connect=10.0),
    )
    assert r.status_code == 200
    pipeline = r.json().get("pipeline_24mo", {})
    by_class = pipeline.get("by_class", {})
    # Не должно быть так что unknown == 100% (это означало бы regression #165)
    if by_class:
        total = sum(by_class.values())
        unknown_share = by_class.get("unknown", 0) / total if total > 0 else 0
        assert unknown_share < 0.9, (
            f"pipeline_by_class.unknown == {unknown_share:.1%} — "
            "regression of #165? Should JOIN on objective_lots."
        )


# ─────────────────────────────────────────────────────────────────────────────
# Admin endpoints (auth required — skip if no token)
# ─────────────────────────────────────────────────────────────────────────────


@pytest.fixture
def admin_headers() -> dict[str, str]:
    if not ADMIN_TOKEN:
        pytest.skip("PROD_SMOKE_ADMIN_TOKEN env var not set")
    return {"X-Admin-Token": ADMIN_TOKEN}


def test_admin_jobs_settings_returns_200(admin_headers: dict[str, str]) -> None:
    r = httpx.get(
        f"{BASE_URL}/api/v1/admin/jobs/settings",
        headers=admin_headers,
        timeout=TIMEOUT,
    )
    assert r.status_code == 200
    assert isinstance(r.json(), list | dict)


def test_admin_cadastre_jobs_endpoint(admin_headers: dict[str, str]) -> None:
    """PR #171 admin cadastre endpoints."""
    r = httpx.get(
        f"{BASE_URL}/api/v1/admin/cadastre/jobs?limit=5",
        headers=admin_headers,
        timeout=TIMEOUT,
    )
    assert r.status_code == 200
    body = r.json()
    assert isinstance(body, list)
    if body:
        # Validate схема jobs row (PR #171)
        required = ["job_id", "status", "targets_total", "targets_done"]
        for key in required:
            assert key in body[0], f"Missing field '{key}' in job row"


def test_admin_weight_profiles_endpoint(admin_headers: dict[str, str]) -> None:
    """PR #138 weight profiles list."""
    r = httpx.get(
        f"{BASE_URL}/api/v1/admin/site-finder/weight-profiles?user_id=admin",
        headers=admin_headers,
        timeout=TIMEOUT,
    )
    assert r.status_code == 200


# ─────────────────────────────────────────────────────────────────────────────
# Frontend bundle (catches build regressions)
# ─────────────────────────────────────────────────────────────────────────────


def test_admin_scrape_cadastre_page_renders() -> None:
    """PR #172 — new admin UI page must SSR ok."""
    r = httpx.get(f"{BASE_URL}/admin/scrape/cadastre", timeout=TIMEOUT)
    assert r.status_code == 200
    assert "Cadastre" in r.text or "кадастр" in r.text.lower() or "Bulk" in r.text


def test_admin_scrape_geo_page_renders() -> None:
    """Existing pattern — должен работать (PR #172 не должен сломать)."""
    r = httpx.get(f"{BASE_URL}/admin/scrape/geo", timeout=TIMEOUT)
    assert r.status_code == 200


# ─────────────────────────────────────────────────────────────────────────────
# Schema invariants (через DB read через admin endpoints)
# ─────────────────────────────────────────────────────────────────────────────


def test_health_db_connection_works() -> None:
    """`/health` returns 200 если DB reachable + migrations applied."""
    r = httpx.get(f"{BASE_URL}/health?check_db=1", timeout=TIMEOUT)
    if r.status_code == 200 and "db" in r.json():
        assert r.json()["db"] == "ok", f"DB check failed: {r.json()}"


# ─────────────────────────────────────────────────────────────────────────────
# How to run after merge (CI integration)
# ─────────────────────────────────────────────────────────────────────────────

# Add to .github/workflows/post-deploy-smoke.yml:
#
# on:
#   workflow_run:
#     workflows: [deploy.yml]
#     types: [completed]
# jobs:
#   smoke:
#     if: ${{ github.event.workflow_run.conclusion == 'success' }}
#     runs-on: ubuntu-latest
#     steps:
#       - uses: actions/checkout@v4
#       - run: uv sync
#       - run: |
#           cd backend && uv run pytest tests/smoke/ -m prod_smoke -v
#         env:
#           PROD_SMOKE_BASE_URL: https://gendsgn.ru
#           PROD_SMOKE_ADMIN_TOKEN: ${{ secrets.SCRAPE_ADMIN_TOKEN }}
#       - name: Post slack/sentry alert if failed
#         if: failure()
#         run: echo "smoke tests failed — investigate"
