"""Variant A — /estimate accepts client-provided coords, skips server geocode().

The address autocomplete/suggest already resolves lat/lon for addresses that the
server-side geocode() chain can't (DaData-format inputs, dead Yandex key). When the
client passes valid in-EKB coords we build the GeocodeResult directly and skip
geocode() entirely — fixing the `address_not_geocoded` failure class.

Contracts locked here:
  1. Valid in-EKB client coords  → geocode() is NOT awaited, still returns an estimate.
  2. Out-of-bbox coords (Moscow)  → ignored, geocode() IS called.
  3. lat/lon both None            → unchanged, geocode() IS called.

Style mirrors test_estimator_cian_budget.py.
"""

import contextlib
import os

# Settings requires DATABASE_URL at init time. Set dummy DSN before any app import.
os.environ.setdefault("DATABASE_URL", "postgresql://test:test@localhost/test_db")

from unittest.mock import AsyncMock, MagicMock, patch

import anyio


def _make_fake_geo():
    from app.services.geocoder import GeocodeResult

    return GeocodeResult(
        lat=56.838,
        lon=60.595,
        full_address="Свердловская обл., Екатеринбург, ул. Учителей, 18",
        provider="nominatim",
    )


def _make_payload(lat=None, lon=None):
    from app.schemas.trade_in import TradeInEstimateInput

    return TradeInEstimateInput(
        address="ЕКБ, ул. Учителей, 18",
        area_m2=38.8,
        rooms=1,
        floor=4,
        total_floors=16,
        lat=lat,
        lon=lon,
    )


def _patches(geocode_mock):
    """Common downstream mocks so estimate_quality runs offline to completion."""
    return (
        patch("app.services.estimator.geocode", new=geocode_mock),
        patch("app.services.estimator.get_house_metadata", new=AsyncMock(return_value=None)),
        patch("app.services.estimator._fetch_analogs", return_value=([], False, "W")),
        patch("app.services.estimator._fetch_deals", return_value=[]),
        patch("app.services.estimator._get_or_fetch_imv_cached", new=AsyncMock(return_value=None)),
        patch(
            "app.services.estimator._get_or_fetch_yandex_valuation_cached",
            new=AsyncMock(return_value=None),
        ),
        patch(
            "app.services.estimator.estimate_via_cian_valuation",
            new=AsyncMock(return_value=None),
        ),
        patch("app.services.estimator._get_asking_sold_ratio", return_value=(None, None)),
    )


def test_client_coords_in_ekb_skip_geocode() -> None:
    """Valid in-EKB client coords → geocode() NOT awaited, estimate still returned."""
    from app.services.estimator import estimate_quality

    db = MagicMock()
    payload = _make_payload(lat=56.838, lon=60.595)
    geocode_mock = AsyncMock(return_value=_make_fake_geo())

    async def _run() -> None:
        with contextlib.ExitStack() as stack:
            for cm in _patches(geocode_mock):
                stack.enter_context(cm)
            result = await estimate_quality(payload, db)

        geocode_mock.assert_not_awaited()
        assert result.estimate_id is not None

    anyio.run(_run)


def test_out_of_bbox_coords_ignored_geocode_called() -> None:
    """Moscow coords are outside the EKB bbox → ignored, geocode() IS called."""
    from app.services.estimator import estimate_quality

    db = MagicMock()
    payload = _make_payload(lat=55.75, lon=37.61)  # Moscow — out of EKB bbox
    geocode_mock = AsyncMock(return_value=_make_fake_geo())

    async def _run() -> None:
        with contextlib.ExitStack() as stack:
            for cm in _patches(geocode_mock):
                stack.enter_context(cm)
            result = await estimate_quality(payload, db)

        geocode_mock.assert_awaited()
        assert result.estimate_id is not None

    anyio.run(_run)


def test_no_coords_geocode_called() -> None:
    """lat/lon both None → unchanged behaviour, geocode() IS called."""
    from app.services.estimator import estimate_quality

    db = MagicMock()
    payload = _make_payload(lat=None, lon=None)
    geocode_mock = AsyncMock(return_value=_make_fake_geo())

    async def _run() -> None:
        with contextlib.ExitStack() as stack:
            for cm in _patches(geocode_mock):
                stack.enter_context(cm)
            result = await estimate_quality(payload, db)

        geocode_mock.assert_awaited()
        assert result.estimate_id is not None

    anyio.run(_run)
