from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest
import requests

from planemapper.fetcher import FileFixtureFetcher, HttpFetcher

_FIXTURES = Path(__file__).parent / "fixtures" / "aircraft_sample.json"

_FULL_RESPONSE = {
    "aircraft": [
        {
            "hex": "4ca7f2",
            "lat": 53.3498,
            "lon": -6.2603,
            "flight": "EIN123  ",
            "altitude": 12000,
            "category": "A3",
            "track": 270.0,
            "mlat": [],
        }
    ]
}

_DEFAULTS_RESPONSE = {
    "aircraft": [
        {
            "hex": "aabbcc",
            "lat": 51.0,
            "lon": -1.0,
        }
    ]
}

_MLAT_RESPONSE = {
    "aircraft": [
        {
            "hex": "dddddd",
            "lat": 52.0,
            "lon": -2.0,
            "mlat": ["lat", "lon"],
        }
    ]
}


def _mock_get(payload: dict) -> MagicMock:
    mock_resp = MagicMock()
    mock_resp.json.return_value = payload
    mock_resp.raise_for_status = MagicMock()
    return mock_resp


def test_http_fetcher_full_response() -> None:
    with patch("planemapper.fetcher.requests.get", return_value=_mock_get(_FULL_RESPONSE)):
        aircraft = HttpFetcher().fetch()
    assert len(aircraft) == 1
    a = aircraft[0]
    assert a.icao == "4ca7f2"
    assert a.lat == 53.3498
    assert a.lon == -6.2603
    assert a.callsign == "EIN123"
    assert a.altitude_ft == 12000
    assert a.category == "A3"
    assert a.heading == 270.0
    assert a.is_mlat is False
    assert a.is_stale is False


def test_http_fetcher_missing_fields_use_defaults() -> None:
    with patch("planemapper.fetcher.requests.get", return_value=_mock_get(_DEFAULTS_RESPONSE)):
        aircraft = HttpFetcher().fetch()
    assert len(aircraft) == 1
    a = aircraft[0]
    assert a.callsign == ""
    assert a.altitude_ft == 0
    assert a.category == ""
    assert a.is_mlat is False


def test_http_fetcher_altitude_ground_string() -> None:
    payload = {"aircraft": [{"hex": "aaa", "lat": 51.0, "lon": -1.0, "altitude": "ground"}]}
    with patch("planemapper.fetcher.requests.get", return_value=_mock_get(payload)):
        aircraft = HttpFetcher().fetch()
    assert aircraft[0].altitude_ft == 0


def test_http_fetcher_skips_entries_without_position() -> None:
    payload = {"aircraft": [{"hex": "nopos"}, {"hex": "haspos", "lat": 51.0, "lon": -1.0}]}
    with patch("planemapper.fetcher.requests.get", return_value=_mock_get(payload)):
        aircraft = HttpFetcher().fetch()
    assert len(aircraft) == 1
    assert aircraft[0].icao == "haspos"


def test_http_fetcher_propagates_timeout() -> None:
    with patch("planemapper.fetcher.requests.get", side_effect=requests.Timeout):
        with pytest.raises(requests.Timeout):
            HttpFetcher().fetch()


def test_http_fetcher_mlat_flag() -> None:
    with patch("planemapper.fetcher.requests.get", return_value=_mock_get(_MLAT_RESPONSE)):
        aircraft = HttpFetcher().fetch()
    assert aircraft[0].is_mlat is True


def test_file_fixture_fetcher_returns_aircraft() -> None:
    aircraft = FileFixtureFetcher(_FIXTURES).fetch()
    assert len(aircraft) == 4


def test_file_fixture_fetcher_no_network_call() -> None:
    with patch("planemapper.fetcher.requests.get") as mock_get:
        FileFixtureFetcher(_FIXTURES).fetch()
    mock_get.assert_not_called()


def test_file_fixture_fetcher_mlat_aircraft() -> None:
    aircraft = FileFixtureFetcher(_FIXTURES).fetch()
    mlat_aircraft = [a for a in aircraft if a.is_mlat]
    assert len(mlat_aircraft) == 1
    assert mlat_aircraft[0].icao == "4003c3"
