from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Dict, List, cast
from uuid import UUID, uuid4

import pytest
from geoalchemy2.elements import WKTElement
from sqlalchemy.orm import Session

from backend.app.models import StationCreate, StationUpdate
from backend.app.services import stations as stations_service


@dataclass
class DummySession:
    flushed: bool = False
    committed: bool = False
    refreshed: List[object] = field(default_factory=list)

    def flush(self) -> None:
        self.flushed = True

    def refresh(self, instance: object) -> None:  # pragma: no cover - simple setter
        self.refreshed.append(instance)

    def commit(self) -> None:
        self.committed = True


@dataclass
class DummyStation:
    id: UUID
    name: str
    location: WKTElement
    osm_id: str | None
    code: str | None
    elevation_m: float | None
    is_active: bool
    created_at: datetime
    updated_at: datetime


class DummyStationRepository:
    _store: Dict[UUID, DummyStation] = {}

    def __init__(self, session: DummySession) -> None:  # pragma: no cover - simple init
        self.session = session

    @staticmethod
    def _point(latitude: float, longitude: float) -> WKTElement:
        return WKTElement(f"POINT({longitude} {latitude})", srid=4326)

    def list(self) -> list[DummyStation]:
        return list(self._store.values())

    def list_active(self) -> list[DummyStation]:
        return [station for station in self._store.values() if station.is_active]

    def get(self, identifier: UUID) -> DummyStation | None:
        return self._store.get(identifier)

    def create(self, payload: StationCreate) -> DummyStation:
        station = DummyStation(
            id=uuid4(),
            name=payload.name,
            location=self._point(payload.latitude, payload.longitude),
            osm_id=payload.osm_id,
            code=payload.code,
            elevation_m=payload.elevation_m,
            is_active=payload.is_active,
            created_at=datetime.now(timezone.utc),
            updated_at=datetime.now(timezone.utc),
        )
        self._store[station.id] = station
        return station


@pytest.fixture(autouse=True)
def reset_store(monkeypatch: pytest.MonkeyPatch) -> None:
    DummyStationRepository._store = {}
    monkeypatch.setattr(stations_service, "StationRepository", DummyStationRepository)


def test_create_station_persists_and_returns_model(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    session = DummySession()
    payload = StationCreate(
        name="Central",
        latitude=52.52,
        longitude=13.405,
        osm_id="123",
        code="BER",
        elevation_m=34.5,
        is_active=True,
    )

    result = stations_service.create_station(cast(Session, session), payload)

    assert session.flushed is True
    assert session.committed is True
    assert result.name == "Central"
    assert result.latitude == pytest.approx(52.52)
    assert result.longitude == pytest.approx(13.405)
    assert result.osm_id == "123"


def test_update_station_updates_geometry_and_metadata() -> None:
    session = DummySession()
    station_id = uuid4()
    DummyStationRepository._store[station_id] = DummyStation(
        id=station_id,
        name="Old Name",
        location=DummyStationRepository._point(50.0, 8.0),
        osm_id=None,
        code=None,
        elevation_m=None,
        is_active=True,
        created_at=datetime.now(timezone.utc),
        updated_at=datetime.now(timezone.utc),
    )

    payload = StationUpdate(name="New Name", latitude=51.0, longitude=9.0)
    result = stations_service.update_station(
        cast(Session, session), str(station_id), payload
    )

    assert result.name == "New Name"
    assert result.latitude == pytest.approx(51.0)
    assert result.longitude == pytest.approx(9.0)
    assert DummyStationRepository._store[station_id].name == "New Name"


def test_update_station_requires_both_coordinates() -> None:
    session = DummySession()
    station_id = uuid4()
    DummyStationRepository._store[station_id] = DummyStation(
        id=station_id,
        name="Station",
        location=DummyStationRepository._point(50.0, 8.0),
        osm_id=None,
        code=None,
        elevation_m=None,
        is_active=True,
        created_at=datetime.now(timezone.utc),
        updated_at=datetime.now(timezone.utc),
    )

    with pytest.raises(ValueError):
        stations_service.update_station(
            cast(Session, session), str(station_id), StationUpdate(latitude=51.0)
        )


def test_archive_station_marks_inactive() -> None:
    session = DummySession()
    station_id = uuid4()
    DummyStationRepository._store[station_id] = DummyStation(
        id=station_id,
        name="Station",
        location=DummyStationRepository._point(50.0, 8.0),
        osm_id=None,
        code=None,
        elevation_m=None,
        is_active=True,
        created_at=datetime.now(timezone.utc),
        updated_at=datetime.now(timezone.utc),
    )

    result = stations_service.archive_station(cast(Session, session), str(station_id))

    assert result.is_active is False
    assert DummyStationRepository._store[station_id].is_active is False
