"""Stage 1c tests — ТЭП and financial model arithmetic.

Pure-arithmetic unit tests against known footprint geometry: GFA, residential area,
apartment count, FAR, parking, revenue/cost/margin and the IRR-proxy clamp.
"""

from __future__ import annotations

from shapely.geometry import box

from app.schemas.concept import TEAP
from app.services.generative import financial, teap

# Один прямоугольник 20 x 10 = 200 кв.м пятна.
_FOOTPRINT = box(0, 0, 20, 10)


def test_teap_basic_arithmetic() -> None:
    result = teap.compute_teap(
        footprints=[_FOOTPRINT],
        floors=10,
        site_area_sqm=1000.0,
        housing_class="comfort",
    )
    assert result.built_area_sqm == 200.0
    # GFA = пятно * этажность.
    assert result.total_floor_area_sqm == 2000.0
    # FAR = GFA / участок.
    assert result.density == 2.0
    # Нежилое (comfort share 0.05) вырезается из GFA: office = 2000 * 0.05 = 100.
    assert result.office_area_sqm == 100.0
    # Жилая = (GFA − нежилое) * efficiency(comfort=0.78) = 1900 * 0.78 = 1482.
    assert result.residential_area_sqm == 1482.0
    # Квартир = floor(жилая / avg(comfort=55)).
    assert result.apartments_count == int(1482.0 // 55.0)
    # Парковка = ceil(квартир * 1.0).
    assert result.parking_spaces == result.apartments_count


def test_teap_class_changes_efficiency_and_lot() -> None:
    econ = teap.compute_teap(
        footprints=[_FOOTPRINT], floors=10, site_area_sqm=1000.0, housing_class="econom"
    )
    biz = teap.compute_teap(
        footprints=[_FOOTPRINT], floors=10, site_area_sqm=1000.0, housing_class="business"
    )
    # Эконом эффективнее по площади -> больше жилой при той же GFA.
    assert econ.residential_area_sqm > biz.residential_area_sqm
    # Бизнес — крупнее лот -> меньше квартир.
    assert biz.apartments_count < econ.apartments_count
    # Бизнес — выше норма парковки на квартиру.
    assert biz.parking_spaces / max(1, biz.apartments_count) >= 1.4


def test_teap_zero_site_area_no_division_error() -> None:
    result = teap.compute_teap(
        footprints=[_FOOTPRINT], floors=5, site_area_sqm=0.0, housing_class="comfort"
    )
    assert result.density == 0.0


def test_teap_empty_placement_is_zeroed() -> None:
    result = teap.compute_teap(
        footprints=[], floors=9, site_area_sqm=1000.0, housing_class="comfort"
    )
    assert result.built_area_sqm == 0.0
    assert result.total_floor_area_sqm == 0.0
    assert result.apartments_count == 0
    assert result.parking_spaces == 0


def _teap(residential: float, gfa: float, parking: int = 10) -> TEAP:
    return TEAP(
        built_area_sqm=100.0,
        total_floor_area_sqm=gfa,
        residential_area_sqm=residential,
        apartments_count=10,
        density=1.0,
        parking_spaces=parking,
    )


def test_financial_revenue_includes_parking() -> None:
    t = _teap(residential=1000.0, gfa=1300.0, parking=10)
    model = financial.compute_financial(
        teap=t, housing_class="comfort", land_cost_rub=50_000_000.0
    )
    # revenue = жильё (1000 * 145_000) + паркинг comfort (10 * 1_300_000).
    assert model.revenue_residential_rub == 1000.0 * 145_000.0
    assert model.revenue_parking_rub == 10 * 1_300_000.0
    assert model.revenue_rub == model.revenue_residential_rub + model.revenue_parking_rub


def test_financial_cost_cascade_includes_all_lines() -> None:
    t = _teap(residential=1000.0, gfa=1300.0, parking=10)
    model = financial.compute_financial(
        teap=t, housing_class="comfort", land_cost_rub=50_000_000.0
    )
    # СМР = GFA*СМР + паркинг comfort*себест (10 * 1_000_000).
    assert model.construction_rub == 1300.0 * 88_000.0 + 10 * 1_000_000.0
    # Каждая статья каскада > 0 при ненулевых вводных.
    assert model.pir_rub > 0
    assert model.networks_rub > 0
    assert model.developer_services_rub > 0
    assert model.contingency_rub > 0
    assert model.marketing_rub > 0
    assert model.land_rub == 50_000_000.0
    # Итог затрат = сумма всех статей.
    expected_cost = (
        model.construction_rub
        + model.pir_rub
        + model.networks_rub
        + model.developer_services_rub
        + model.contingency_rub
        + model.marketing_rub
        + model.land_rub
    )
    assert abs(model.cost_rub - expected_cost) < 1.0
    assert model.gross_margin_rub == model.revenue_rub - model.cost_rub


def test_financial_net_profit_below_gross_margin_when_profitable() -> None:
    # Прибыльный сценарий: налоги срезают валовую маржу -> net < gross.
    t = _teap(residential=10_000.0, gfa=11_000.0, parking=50)
    model = financial.compute_financial(teap=t, housing_class="comfort", land_cost_rub=None)
    assert model.gross_margin_rub > 0
    assert model.vat_rub > 0
    assert model.profit_tax_rub > 0
    assert model.net_profit_rub < model.profit_before_tax_rub < model.gross_margin_rub
    # ROI и маржа% согласованы с чистой прибылью (поля округлены до 4 знаков).
    assert model.roi == round(model.net_profit_rub / model.cost_rub, 4)
    assert model.margin_pct == round(model.net_profit_rub / model.revenue_rub, 4)


def test_financial_no_profit_tax_on_loss() -> None:
    # Убыток: налог на прибыль = 0, НДС = 0; net == pbt == gross_margin.
    t = _teap(residential=1.0, gfa=10_000.0, parking=0)
    model = financial.compute_financial(teap=t, housing_class="business", land_cost_rub=None)
    assert model.gross_margin_rub < 0
    assert model.profit_tax_rub == 0.0
    assert model.vat_rub == 0.0
    assert model.profit_before_tax_rub == model.gross_margin_rub
    assert model.net_profit_rub == model.gross_margin_rub


def test_financial_parking_zero_yields_zero_parking_lines() -> None:
    t = _teap(residential=1000.0, gfa=1300.0, parking=0)
    model = financial.compute_financial(teap=t, housing_class="comfort", land_cost_rub=None)
    assert model.revenue_parking_rub == 0.0
    # СМР без паркинговой составляющей = ровно GFA*СМР.
    assert model.construction_rub == 1300.0 * 88_000.0


def test_financial_land_cost_optional() -> None:
    t = _teap(residential=1000.0, gfa=1300.0)
    no_land = financial.compute_financial(teap=t, housing_class="comfort", land_cost_rub=None)
    with_land = financial.compute_financial(
        teap=t, housing_class="comfort", land_cost_rub=10_000_000.0
    )
    # Земля увеличивает затраты ровно на свою стоимость.
    assert with_land.cost_rub - no_land.cost_rub == 10_000_000.0
    assert with_land.land_rub == 10_000_000.0
    assert no_land.land_rub == 0.0


def test_financial_irr_proxy_clamped_and_flagged() -> None:
    # Огромная маржа -> irr-proxy зажат в [-1, 1]; флаг proxy выставлен.
    t = _teap(residential=100_000.0, gfa=1.0, parking=0)
    model = financial.compute_financial(teap=t, housing_class="business", land_cost_rub=None)
    assert -1.0 <= model.irr <= 1.0
    assert model.irr_is_proxy is True


def test_financial_zero_cost_no_division_error() -> None:
    t = _teap(residential=0.0, gfa=0.0, parking=0)
    model = financial.compute_financial(teap=t, housing_class="comfort", land_cost_rub=None)
    assert model.cost_rub == 0.0
    assert model.revenue_rub == 0.0
    assert model.roi == 0.0
    assert model.margin_pct == 0.0
    assert model.irr == 0.0


def test_financial_backward_compat_fields_present() -> None:
    t = _teap(residential=1000.0, gfa=1300.0)
    model = financial.compute_financial(teap=t, housing_class="comfort", land_cost_rub=None)
    # Старые summary-поля контракта остаются.
    assert isinstance(model.revenue_rub, float)
    assert isinstance(model.cost_rub, float)
    assert isinstance(model.gross_margin_rub, float)
    assert isinstance(model.irr, float)
    # PR-3 DCF-поля additive — присутствуют с дефолтами.
    assert isinstance(model.npv_rub, float)
    assert model.discount_rate_used == 0.15
    assert model.schedule_is_default is True
    assert model.payback_months is None or isinstance(model.payback_months, float)


# ── PR-2: рыночная калибровка цены продажи жилья ───────────────────────────────


def test_financial_no_market_price_uses_class_norm() -> None:
    # Без market_price_per_sqm — норматив класса comfort (145_000), флаг не калибровано.
    t = _teap(residential=1000.0, gfa=1300.0)
    model = financial.compute_financial(teap=t, housing_class="comfort", land_cost_rub=None)
    assert model.price_per_sqm_used == 145_000.0
    assert model.price_is_calibrated is False
    assert model.price_source == "class_norm"
    # Выручка жилья считается по нормативу.
    assert model.revenue_residential_rub == 1000.0 * 145_000.0


def test_financial_market_price_overrides_class_norm() -> None:
    # market_price_per_sqm передан → используется он, флаг калибровано, source проброшен.
    t = _teap(residential=1000.0, gfa=1300.0)
    model = financial.compute_financial(
        teap=t,
        housing_class="comfort",
        land_cost_rub=None,
        market_price_per_sqm=180_000.0,
        price_source="objective_district_median",
    )
    assert model.price_per_sqm_used == 180_000.0
    assert model.price_is_calibrated is True
    assert model.price_source == "objective_district_median"
    # Выручка жилья считается по рыночной цене, НЕ по нормативу класса.
    assert model.revenue_residential_rub == 1000.0 * 180_000.0


def test_financial_market_price_does_not_calibrate_parking_or_smr() -> None:
    # Калибруем ТОЛЬКО цену жилья: паркинг (класс comfort) и себестоимость СМР
    # (88_000) — норма, не зависят от market_price_per_sqm.
    t = _teap(residential=1000.0, gfa=1300.0, parking=10)
    model = financial.compute_financial(
        teap=t,
        housing_class="comfort",
        land_cost_rub=None,
        market_price_per_sqm=200_000.0,
        price_source="district_reference",
    )
    # Паркинг — норматив класса comfort (10 * 1_300_000), не зависит от цены жилья.
    assert model.revenue_parking_rub == 10 * 1_300_000.0
    # СМР — норматив класса comfort (GFA*88_000 + паркинг*1_000_000).
    assert model.construction_rub == 1300.0 * 88_000.0 + 10 * 1_000_000.0


def test_financial_none_market_price_forces_class_norm_source() -> None:
    # Даже если source передан, при market_price_per_sqm=None он форсится в class_norm —
    # нельзя выдавать норматив за рыночные данные.
    t = _teap(residential=1000.0, gfa=1300.0)
    model = financial.compute_financial(
        teap=t,
        housing_class="comfort",
        land_cost_rub=None,
        market_price_per_sqm=None,
        price_source="objective_district_median",
    )
    assert model.price_is_calibrated is False
    assert model.price_source == "class_norm"
    assert model.price_per_sqm_used == 145_000.0


# ── Классозависимая экономика паркинга (econom наземный / business подземный) ────


def test_financial_parking_econom_uses_surface_price_and_cost() -> None:
    # Эконом — наземный паркинг: цена 800k, себестоимость 350k за м/м.
    t = _teap(residential=1000.0, gfa=1300.0, parking=10)
    model = financial.compute_financial(teap=t, housing_class="econom", land_cost_rub=None)
    assert model.revenue_parking_rub == 10 * 800_000.0
    # СМР = GFA*72_000 (econom) + паркинг наземный 10 * 350_000.
    assert model.construction_rub == 1300.0 * 72_000.0 + 10 * 350_000.0


def test_financial_parking_comfort_uses_mixed_price_and_cost() -> None:
    # Комфорт — частично подземный: цена 1.3М, себестоимость 1.0М за м/м.
    t = _teap(residential=1000.0, gfa=1300.0, parking=10)
    model = financial.compute_financial(teap=t, housing_class="comfort", land_cost_rub=None)
    assert model.revenue_parking_rub == 10 * 1_300_000.0
    assert model.construction_rub == 1300.0 * 88_000.0 + 10 * 1_000_000.0


def test_financial_parking_business_matches_excel_avangardnaya13() -> None:
    # РЕГРЕСС-ПИН: бизнес-паркинг подземный — без изменений vs Excel «Авангардная 13»
    # (продажа 1.9М / себестоимость 1.8М за м/м). Любой дрейф этих чисел — провал.
    t = _teap(residential=1000.0, gfa=1300.0, parking=10)
    model = financial.compute_financial(teap=t, housing_class="business", land_cost_rub=None)
    assert model.revenue_parking_rub == 10 * 1_900_000.0
    assert model.construction_rub == 1300.0 * 120_000.0 + 10 * 1_800_000.0
    # Прямая сверка с константами-словарями (теперь dict, не скаляр).
    assert financial._PARKING_PRICE_PER_SPOT["business"] == 1_900_000.0
    assert financial._PARKING_COST_PER_SPOT["business"] == 1_800_000.0


def test_financial_parking_margin_positive_for_all_classes() -> None:
    # Цена > себестоимости во всех классах → паркинг не убыточен ни в одном классе.
    for hc in ("econom", "comfort", "business"):
        price = financial._PARKING_PRICE_PER_SPOT[hc]  # type: ignore[index]
        cost = financial._PARKING_COST_PER_SPOT[hc]  # type: ignore[index]
        assert price > cost, hc
    # Конкретные маржи из спецификации: econom +450k, comfort +300k, business +100k.
    assert financial._PARKING_PRICE_PER_SPOT["econom"] - financial._PARKING_COST_PER_SPOT[
        "econom"
    ] == 450_000.0
    assert financial._PARKING_PRICE_PER_SPOT["comfort"] - financial._PARKING_COST_PER_SPOT[
        "comfort"
    ] == 300_000.0
    assert financial._PARKING_PRICE_PER_SPOT["business"] - financial._PARKING_COST_PER_SPOT[
        "business"
    ] == 100_000.0


def test_financial_econom_parking_cheaper_than_business() -> None:
    # Тот же участок: econom-паркинг (наземный) даёт МЕНЬШЕ паркинговой выручки и
    # МЕНЬШЕ паркинговой себестоимости, чем business (подземный) — проверка, что
    # классозависимость реально применяется в каскаде.
    t = _teap(residential=1000.0, gfa=1300.0, parking=10)
    econ = financial.compute_financial(teap=t, housing_class="econom", land_cost_rub=None)
    biz = financial.compute_financial(teap=t, housing_class="business", land_cost_rub=None)
    assert econ.revenue_parking_rub < biz.revenue_parking_rub
    # Паркинговая часть СМР: econom 350k/м < business 1.8М/м.
    econ_parking_smr = econ.construction_rub - 1300.0 * 72_000.0
    biz_parking_smr = biz.construction_rub - 1300.0 * 120_000.0
    assert econ_parking_smr < biz_parking_smr


# ── Stage 2a (#1965): synthesize_teap_from_program (скалярное пятно × этажность) ──


def test_synthesize_program_gfa_is_footprint_times_floors() -> None:
    # GFA = суммарное пятно × этажность; пятно = total_footprint_sqm как есть.
    result = teap.synthesize_teap_from_program(
        total_footprint_sqm=3000.0,
        floors=12,
        site_area_sqm=10_000.0,
        housing_class="comfort",
        sections=2,
    )
    assert result.built_area_sqm == 3000.0
    assert result.total_floor_area_sqm == 3000.0 * 12  # 36_000
    # FAR = GFA / участок.
    assert result.density == round(36_000.0 / 10_000.0, 3)


def test_synthesize_program_matches_compute_teap_for_equivalent_input() -> None:
    # synthesize_teap_from_program(footprint, floors) ДОЛЖЕН дать ровно тот же ТЭП,
    # что compute_teap из одного прямоугольного пятна той же площади — единая логика.
    footprint = box(0, 0, 50, 40)  # 2000 кв.м
    from_program = teap.synthesize_teap_from_program(
        total_footprint_sqm=2000.0,
        floors=9,
        site_area_sqm=5000.0,
        housing_class="comfort",
    )
    from_geometry = teap.compute_teap(
        footprints=[footprint],
        floors=9,
        site_area_sqm=5000.0,
        housing_class="comfort",
    )
    assert from_program == from_geometry


def test_synthesize_program_office_carveout_and_efficiency_by_class() -> None:
    # Нежилое вырезается из GFA (comfort share 0.05), жилая = (GFA−нежилое)*efficiency.
    result = teap.synthesize_teap_from_program(
        total_footprint_sqm=2000.0,
        floors=10,
        site_area_sqm=5000.0,
        housing_class="comfort",
    )
    gfa = 2000.0 * 10  # 20_000
    assert result.office_area_sqm == round(gfa * 0.05, 1)  # 1000
    assert result.residential_area_sqm == round((gfa - gfa * 0.05) * 0.78, 1)


def test_synthesize_program_class_changes_efficiency_and_lot() -> None:
    common = dict(total_footprint_sqm=2000.0, floors=10, site_area_sqm=5000.0)
    econ = teap.synthesize_teap_from_program(housing_class="econom", **common)  # type: ignore[arg-type]
    biz = teap.synthesize_teap_from_program(housing_class="business", **common)  # type: ignore[arg-type]
    # Эконом эффективнее (0.82 vs 0.72) + нет нежилого carve-out → больше жилой.
    assert econ.residential_area_sqm > biz.residential_area_sqm
    # Бизнес — крупнее средний лот → меньше квартир.
    assert biz.apartments_count < econ.apartments_count
    # Бизнес — выше норма парковки на квартиру.
    assert biz.parking_spaces / max(1, biz.apartments_count) >= 1.4


def test_synthesize_program_zero_site_area_no_division_error() -> None:
    result = teap.synthesize_teap_from_program(
        total_footprint_sqm=1000.0,
        floors=5,
        site_area_sqm=0.0,
        housing_class="comfort",
    )
    assert result.density == 0.0


def test_synthesize_program_sections_do_not_affect_teap() -> None:
    # sections — метаданные программы; площади уже свёрнуты → ТЭП от них не зависит.
    one = teap.synthesize_teap_from_program(
        total_footprint_sqm=2000.0, floors=10, site_area_sqm=5000.0,
        housing_class="comfort", sections=1,
    )
    six = teap.synthesize_teap_from_program(
        total_footprint_sqm=2000.0, floors=10, site_area_sqm=5000.0,
        housing_class="comfort", sections=6,
    )
    assert one == six


def test_synthesize_program_then_financial_is_coherent() -> None:
    # Сквозь финмодель: жизнеспособная программа → выручка > 0, NPV посчитан.
    t = teap.synthesize_teap_from_program(
        total_footprint_sqm=3000.0,
        floors=12,
        site_area_sqm=10_000.0,
        housing_class="comfort",
    )
    model = financial.compute_financial(
        teap=t,
        housing_class="comfort",
        land_cost_rub=None,
        market_price_per_sqm=150_000.0,
        price_source="objective_district_median",
    )
    assert model.revenue_rub > 0
    assert model.cost_rub > 0
    assert isinstance(model.npv_rub, float)
    # Цена прокинута в выручку жилья.
    assert model.price_per_sqm_used == 150_000.0
    assert model.price_is_calibrated is True
