"""Maps & GPS backend regression suite.

Covers:
- /api/maps/dashboard widget counts
- /api/maps/parties/{pid}/location PATCH-like POST + Party round-trip
- /api/maps/nearby haversine math
- /api/maps/route nearest-neighbour TSP
- /api/maps/visits POST + GET
- /api/maps/salesman/ping + /track + /salesmen/live
- /api/maps/geocode/proxy + /reverse-geocode (Nominatim — soft-skip on 502)
"""
import os
import math
import time
import pytest

BASE_URL = os.environ.get(
    "REACT_APP_BACKEND_URL", "https://offline-billing-pro-2.preview.emergentagent.com"
).rstrip("/")


# ============================== Dashboard ==============================
class TestMapsDashboard:
    def test_dashboard_keys_and_types(self, admin_session):
        r = admin_session.get(f"{BASE_URL}/api/maps/dashboard", timeout=30)
        assert r.status_code == 200, r.text
        data = r.json()
        for k in ("mapped_customers", "todays_visits", "active_salesmen", "deliveries_in_progress"):
            assert k in data, f"Missing key {k}"
            assert isinstance(data[k], int), f"{k} should be int, got {type(data[k])}"
            assert data[k] >= 0


# ============================== Geocode ==============================
class TestGeocode:
    def test_geocode_proxy_returns_list(self, admin_session):
        r = admin_session.get(
            f"{BASE_URL}/api/maps/geocode/proxy", params={"q": "Panaji Goa"}, timeout=30
        )
        if r.status_code == 502:
            pytest.skip("Nominatim upstream unavailable (502) — soft skip")
        assert r.status_code == 200, r.text
        data = r.json()
        assert isinstance(data, list)
        if data:
            first = data[0]
            assert "label" in first
            assert "latitude" in first and isinstance(first["latitude"], (int, float))
            assert "longitude" in first and isinstance(first["longitude"], (int, float))
            # Panaji is roughly at 15.5N, 73.8E
            assert any(15.0 < d["latitude"] < 16.5 and 73.0 < d["longitude"] < 74.5 for d in data), \
                "Expected at least one result near Panaji"

    def test_geocode_proxy_validation(self, admin_session):
        # Too-short q rejected
        r = admin_session.get(f"{BASE_URL}/api/maps/geocode/proxy", params={"q": "a"}, timeout=30)
        assert r.status_code == 422

    def test_reverse_geocode(self, admin_session):
        r = admin_session.get(
            f"{BASE_URL}/api/maps/reverse-geocode",
            params={"lat": 15.4909, "lng": 73.8278},
            timeout=30,
        )
        if r.status_code == 502:
            pytest.skip("Nominatim upstream unavailable (502) — soft skip")
        assert r.status_code == 200, r.text
        data = r.json()
        assert "label" in data
        assert "address" in data


# ====================== Party location round-trip ======================
@pytest.fixture(scope="module")
def test_party_id(admin_session, default_company_id):
    """Create a TEST party for location tests; cleanup after."""
    payload = {
        "name": "TEST_MapsParty",
        "type": "customer",
        "phone": "9000000111",
    }
    r = admin_session.post(
        f"{BASE_URL}/api/parties?company_id={default_company_id}", json=payload, timeout=30
    )
    assert r.status_code in (200, 201), r.text
    pid = r.json().get("id") or r.json().get("_id")
    assert pid
    yield pid
    admin_session.delete(f"{BASE_URL}/api/parties/{pid}", timeout=30)


class TestPartyLocation:
    def test_set_party_location_and_persist(self, admin_session, test_party_id, default_company_id):
        body = {"latitude": 15.4909, "longitude": 73.8278, "geofence_radius_m": 250}
        r = admin_session.post(
            f"{BASE_URL}/api/maps/parties/{test_party_id}/location", json=body, timeout=30
        )
        assert r.status_code == 200, r.text
        data = r.json()
        assert data.get("ok") is True
        assert data["latitude"] == 15.4909
        assert data["longitude"] == 73.8278

        # Verify persistence via GET /api/parties?company_id=...
        # We'll filter the list by company_id (faster + matches scoping)
        from conftest import BASE_URL as _b  # noqa: F401
        rg = admin_session.get(
            f"{BASE_URL}/api/parties", params={"company_id": default_company_id}, timeout=30
        )
        assert rg.status_code == 200, rg.text
        parties = rg.json()
        party = next((p for p in parties if p.get("id") == test_party_id), None)
        assert party is not None, (
            f"Party {test_party_id} not found in list (got {len(parties)} parties)"
        )
        assert party.get("latitude") == 15.4909
        assert party.get("longitude") == 73.8278
        assert party.get("geofence_radius_m") == 250

    def test_set_location_invalid_lat(self, admin_session, test_party_id):
        r = admin_session.post(
            f"{BASE_URL}/api/maps/parties/{test_party_id}/location",
            json={"latitude": 999, "longitude": 73.0},
            timeout=30,
        )
        assert r.status_code == 422

    def test_set_location_missing_party(self, admin_session):
        r = admin_session.post(
            f"{BASE_URL}/api/maps/parties/507f1f77bcf86cd799439011/location",
            json={"latitude": 15.0, "longitude": 73.0},
            timeout=30,
        )
        assert r.status_code == 404


# ============================== Nearby ==============================
class TestNearby:
    def test_nearby_finds_party(self, admin_session, test_party_id):
        # Ensure location set
        admin_session.post(
            f"{BASE_URL}/api/maps/parties/{test_party_id}/location",
            json={"latitude": 15.4909, "longitude": 73.8278},
            timeout=30,
        )
        # Query within 5km of Panaji
        r = admin_session.post(
            f"{BASE_URL}/api/maps/nearby",
            json={"latitude": 15.50, "longitude": 73.83, "radius_km": 5},
            timeout=30,
        )
        assert r.status_code == 200, r.text
        data = r.json()
        assert isinstance(data, list)
        ids = [d["id"] for d in data]
        assert test_party_id in ids, f"Created party not returned within 5km. Got {ids[:5]}"
        rec = next(d for d in data if d["id"] == test_party_id)
        assert "distance_km" in rec
        # Distance should be < 2km between (15.50, 73.83) and (15.4909, 73.8278)
        assert rec["distance_km"] < 2.0
        # List sorted by distance
        dists = [d["distance_km"] for d in data]
        assert dists == sorted(dists)

    def test_nearby_excludes_far(self, admin_session, test_party_id):
        # Query 5km away from Mumbai — should NOT include Panaji
        r = admin_session.post(
            f"{BASE_URL}/api/maps/nearby",
            json={"latitude": 19.0760, "longitude": 72.8777, "radius_km": 5},
            timeout=30,
        )
        assert r.status_code == 200
        ids = [d["id"] for d in r.json()]
        assert test_party_id not in ids


# ============================== Route ==============================
class TestRoute:
    def test_route_orders_by_nearest(self, admin_session):
        # Start at Panaji. Stops: Margao (far south), Mapusa (close north), Vasco (close).
        payload = {
            "start": {"latitude": 15.4909, "longitude": 73.8278},
            "stops": [
                {"id": "margao", "name": "Margao", "latitude": 15.2832, "longitude": 73.9862},
                {"id": "mapusa", "name": "Mapusa", "latitude": 15.5937, "longitude": 73.8142},
                {"id": "vasco", "name": "Vasco", "latitude": 15.3962, "longitude": 73.8167},
            ],
        }
        r = admin_session.post(f"{BASE_URL}/api/maps/route", json=payload, timeout=30)
        assert r.status_code == 200, r.text
        data = r.json()
        assert "order" in data and len(data["order"]) == 3
        assert "total_km" in data and data["total_km"] > 0
        assert "eta_minutes" in data and data["eta_minutes"] > 0
        # Mapusa & Vasco are both ~10-12km from Panaji; first stop should be the closest to Panaji.
        from_panaji = {
            "mapusa": ((15.5937 - 15.4909) ** 2 + (73.8142 - 73.8278) ** 2) ** 0.5,
            "vasco": ((15.3962 - 15.4909) ** 2 + (73.8167 - 73.8278) ** 2) ** 0.5,
            "margao": ((15.2832 - 15.4909) ** 2 + (73.9862 - 73.8278) ** 2) ** 0.5,
        }
        expected_first = min(from_panaji, key=from_panaji.get)
        assert data["order"][0]["id"] == expected_first, (
            f"Expected nearest-first to start at {expected_first}, got {data['order'][0]['id']}"
        )
        # Margao (far south) should be last
        assert data["order"][-1]["id"] == "margao"
        # Each leg has leg_km
        for leg in data["order"]:
            assert "leg_km" in leg and leg["leg_km"] >= 0
        # ETA at 30 km/h check
        expected_eta = round((data["total_km"] / 30.0) * 60, 1)
        assert math.isclose(data["eta_minutes"], expected_eta, abs_tol=0.2)


# ============================== Visits ==============================
class TestVisits:
    def test_visit_log_and_list(self, admin_session, test_party_id):
        # Ensure party has location
        admin_session.post(
            f"{BASE_URL}/api/maps/parties/{test_party_id}/location",
            json={"latitude": 15.4909, "longitude": 73.8278},
            timeout=30,
        )
        body = {
            "party_id": test_party_id,
            "type": "check_in",
            "latitude": 15.4912,
            "longitude": 73.8280,
            "note": "TEST_visit",
        }
        r = admin_session.post(f"{BASE_URL}/api/maps/visits", json=body, timeout=30)
        assert r.status_code == 200, r.text
        v = r.json()
        assert v["party_id"] == test_party_id
        assert v["type"] == "check_in"
        assert "id" in v
        assert "user_email" in v
        assert "timestamp" in v
        assert v.get("party_name") == "TEST_MapsParty"
        assert v.get("distance_from_party_m") is not None
        assert v["distance_from_party_m"] < 200  # Should be ~30-50m

        # Now GET /visits
        rg = admin_session.get(
            f"{BASE_URL}/api/maps/visits", params={"party_id": test_party_id}, timeout=30
        )
        assert rg.status_code == 200
        visits = rg.json()
        assert isinstance(visits, list)
        assert any(x["id"] == v["id"] for x in visits)

    def test_visit_invalid_type(self, admin_session, test_party_id):
        r = admin_session.post(
            f"{BASE_URL}/api/maps/visits",
            json={
                "party_id": test_party_id,
                "type": "invalid_type",
                "latitude": 15.0,
                "longitude": 73.0,
            },
            timeout=30,
        )
        assert r.status_code == 422


# ============================ Salesman GPS ============================
class TestSalesmanGPS:
    def test_ping_and_track(self, admin_session):
        r = admin_session.post(
            f"{BASE_URL}/api/maps/salesman/ping",
            json={"latitude": 15.4909, "longitude": 73.8278, "accuracy_m": 12.5},
            timeout=30,
        )
        assert r.status_code == 200, r.text
        data = r.json()
        assert data.get("ok") is True
        assert "timestamp" in data
        time.sleep(0.3)
        # Second ping
        r2 = admin_session.post(
            f"{BASE_URL}/api/maps/salesman/ping",
            json={"latitude": 15.4920, "longitude": 73.8290, "accuracy_m": 8.0},
            timeout=30,
        )
        assert r2.status_code == 200

        # Track
        admin_email = os.environ.get("TEST_ADMIN_EMAIL", "regalmarketing2024@gmail.com")
        rt = admin_session.get(
            f"{BASE_URL}/api/maps/salesman/{admin_email}/track", params={"days": 1}, timeout=30
        )
        assert rt.status_code == 200, rt.text
        pings = rt.json()
        assert isinstance(pings, list)
        assert len(pings) >= 2

    def test_salesmen_live_admin(self, admin_session):
        # Must have at least our admin after pings
        r = admin_session.get(f"{BASE_URL}/api/maps/salesmen/live", timeout=30)
        assert r.status_code == 200, r.text
        data = r.json()
        assert isinstance(data, list)
        admin_email = os.environ.get("TEST_ADMIN_EMAIL", "regalmarketing2024@gmail.com")
        assert any(u["user_email"] == admin_email for u in data), (
            f"Expected admin in live salesmen, got {[u.get('user_email') for u in data]}"
        )
        rec = next(u for u in data if u["user_email"] == admin_email)
        assert "latitude" in rec and "longitude" in rec
