from __future__ import annotations

from unittest.mock import AsyncMock, patch
from urllib.parse import unquote

import pytest

from agrobr.desmatamento.client import _build_state_cql, _build_wfs_url, _uf_to_estado
from agrobr.exceptions import SourceUnavailableError


class TestBuildWfsUrl:
    def test_basic_url(self):
        url = _build_wfs_url("workspace1", "layer1", ["col1", "col2"])
        assert "workspace1/ows" in url
        assert "typeName=workspace1:layer1" in url
        assert "propertyName=col1,col2" in url
        assert "outputFormat=csv" in url

    def test_with_cql_filter(self):
        url = _build_wfs_url("ws", "ly", ["c1"], cql_filter="year=2023")
        assert "CQL_FILTER=" in url

    def test_max_features(self):
        url = _build_wfs_url("ws", "ly", ["c1"], max_features=100)
        assert "maxFeatures=100" in url

    def test_default_max_features(self):
        url = _build_wfs_url("ws", "ly", ["c1"])
        assert "maxFeatures=50000" in url

    def test_output_format_json(self):
        url = _build_wfs_url("ws", "ly", ["c1"], output_format="application/json")
        assert "outputFormat=application/json" in url

    def test_output_format_default_csv(self):
        url = _build_wfs_url("ws", "ly", ["c1"])
        assert "outputFormat=csv" in url


class TestUfToEstado:
    def test_known_uf(self):
        assert _uf_to_estado("MT") is not None

    def test_lowercase_uf(self):
        assert _uf_to_estado("mt") is not None

    def test_unknown_uf(self):
        assert _uf_to_estado("XX") is None

    def test_sp(self):
        result = _uf_to_estado("SP")
        assert result is not None


class TestBuildStateCql:
    def test_or_condition(self):
        cql = _build_state_cql("MT")
        assert "state='MT'" in cql
        assert "state='MATO GROSSO'" in cql
        assert cql.startswith("(")
        assert " OR " in cql

    def test_unknown_uf_no_or(self):
        cql = _build_state_cql("XX")
        assert cql == "state='XX'"
        assert "OR" not in cql

    def test_case_insensitive(self):
        cql = _build_state_cql("mt")
        assert "state='MT'" in cql
        assert "state='MATO GROSSO'" in cql

    def test_uf_without_name(self):
        cql = _build_state_cql("DF")
        assert "state='DF'" in cql
        assert "state='DISTRITO FEDERAL'" in cql


class TestFetchProdes:
    @pytest.mark.asyncio
    async def test_unsupported_bioma(self):
        with pytest.raises(SourceUnavailableError, match="nao suportado"):
            from agrobr.desmatamento.client import fetch_prodes

            await fetch_prodes("bioma_invalido")

    @pytest.mark.asyncio
    async def test_successful_fetch(self):
        from agrobr.desmatamento.client import fetch_prodes

        with patch(
            "agrobr.desmatamento.client.fetch_wfs",
            new_callable=AsyncMock,
            return_value=b"x" * 5000,
        ):
            content, url = await fetch_prodes("Amazônia")
        assert len(content) >= 5000
        assert "prodes-amazon-nb" in url
        assert "yearly_deforestation_biome" in url

    @pytest.mark.asyncio
    async def test_with_ano_and_uf(self):
        from agrobr.desmatamento.client import fetch_prodes

        with patch(
            "agrobr.desmatamento.client.fetch_wfs",
            new_callable=AsyncMock,
            return_value=b"x" * 5000,
        ):
            content, url = await fetch_prodes("Amazônia", ano=2023, uf="MT")
        assert len(content) >= 5000


class TestFetchDeter:
    @pytest.mark.asyncio
    async def test_unsupported_bioma(self):
        from agrobr.desmatamento.client import fetch_deter

        with pytest.raises(SourceUnavailableError, match="nao suportado"):
            await fetch_deter("bioma_invalido")

    @pytest.mark.asyncio
    async def test_successful_fetch(self):
        from agrobr.desmatamento.client import fetch_deter

        with patch(
            "agrobr.desmatamento.client.fetch_wfs",
            new_callable=AsyncMock,
            return_value=b"x" * 5000,
        ):
            content, url = await fetch_deter(
                "Amazônia", uf="PA", data_inicio="2024-01-01", data_fim="2024-06-01"
            )
        assert len(content) >= 5000

    @pytest.mark.asyncio
    async def test_404_raises(self):
        from agrobr.desmatamento.client import fetch_deter

        with (
            patch(
                "agrobr.desmatamento.client.fetch_wfs",
                new_callable=AsyncMock,
                side_effect=SourceUnavailableError(
                    source="desmatamento", url="", last_error="HTTP 404"
                ),
            ),
            pytest.raises(SourceUnavailableError),
        ):
            await fetch_deter("Amazônia")


class TestFetchDeterGeo:
    @pytest.mark.asyncio
    async def test_successful_fetch(self):
        from agrobr.desmatamento.client import fetch_deter_geo

        with patch(
            "agrobr.desmatamento.client.fetch_wfs",
            new_callable=AsyncMock,
            return_value=b"x" * 5000,
        ):
            content, url = await fetch_deter_geo("Amazônia")
        assert len(content) >= 5000

    @pytest.mark.asyncio
    async def test_url_max_features_10000(self):
        from agrobr.desmatamento.client import fetch_deter_geo

        with patch(
            "agrobr.desmatamento.client.fetch_wfs",
            new_callable=AsyncMock,
            return_value=b"x" * 5000,
        ):
            content, url = await fetch_deter_geo("Amazônia")
        assert "maxFeatures=10000" in url

    @pytest.mark.asyncio
    async def test_url_contains_geom_column_amz(self):
        from agrobr.desmatamento.client import fetch_deter_geo

        with patch(
            "agrobr.desmatamento.client.fetch_wfs",
            new_callable=AsyncMock,
            return_value=b"x" * 5000,
        ):
            _, url = await fetch_deter_geo("Amazônia")
        assert "propertyName=geom," in url

    @pytest.mark.asyncio
    async def test_url_contains_geom_column_cerrado(self):
        from agrobr.desmatamento.client import fetch_deter_geo

        with patch(
            "agrobr.desmatamento.client.fetch_wfs",
            new_callable=AsyncMock,
            return_value=b"x" * 5000,
        ):
            _, url = await fetch_deter_geo("Cerrado")
        assert "propertyName=st_multi," in url

    @pytest.mark.asyncio
    async def test_url_output_format_json(self):
        from agrobr.desmatamento.client import fetch_deter_geo

        with patch(
            "agrobr.desmatamento.client.fetch_wfs",
            new_callable=AsyncMock,
            return_value=b"x" * 5000,
        ):
            _, url = await fetch_deter_geo("Amazônia")
        assert "outputFormat=application/json" in url

    @pytest.mark.asyncio
    async def test_unsupported_bioma(self):
        from agrobr.desmatamento.client import fetch_deter_geo

        with pytest.raises(SourceUnavailableError, match="nao suportado"):
            await fetch_deter_geo("bioma_invalido")


class TestFetchProdesGeo:
    @pytest.mark.asyncio
    async def test_successful_fetch(self):
        from agrobr.desmatamento.client import fetch_prodes_geo

        with patch(
            "agrobr.desmatamento.client.fetch_wfs",
            new_callable=AsyncMock,
            return_value=b"x" * 5000,
        ):
            content, url = await fetch_prodes_geo("Cerrado")
        assert len(content) >= 5000

    @pytest.mark.asyncio
    async def test_max_features_10000(self):
        from agrobr.desmatamento.client import fetch_prodes_geo

        with patch(
            "agrobr.desmatamento.client.fetch_wfs",
            new_callable=AsyncMock,
            return_value=b"x" * 5000,
        ):
            _, url = await fetch_prodes_geo("Cerrado")
        assert "maxFeatures=10000" in url

    @pytest.mark.asyncio
    async def test_geom_column_in_url(self):
        from agrobr.desmatamento.client import fetch_prodes_geo

        with patch(
            "agrobr.desmatamento.client.fetch_wfs",
            new_callable=AsyncMock,
            return_value=b"x" * 5000,
        ):
            _, url = await fetch_prodes_geo("Cerrado")
        assert "propertyName=geom," in url

    @pytest.mark.asyncio
    async def test_output_format_json(self):
        from agrobr.desmatamento.client import fetch_prodes_geo

        with patch(
            "agrobr.desmatamento.client.fetch_wfs",
            new_callable=AsyncMock,
            return_value=b"x" * 5000,
        ):
            _, url = await fetch_prodes_geo("Cerrado")
        assert "outputFormat=application/json" in url

    @pytest.mark.asyncio
    async def test_unsupported_bioma(self):
        from agrobr.desmatamento.client import fetch_prodes_geo

        with pytest.raises(SourceUnavailableError, match="nao suportado"):
            await fetch_prodes_geo("bioma_invalido")

    @pytest.mark.asyncio
    async def test_with_ano_and_uf(self):
        from agrobr.desmatamento.client import fetch_prodes_geo

        with patch(
            "agrobr.desmatamento.client.fetch_wfs",
            new_callable=AsyncMock,
            return_value=b"x" * 5000,
        ):
            _, url = await fetch_prodes_geo("Cerrado", ano=2023, uf="MT")
        decoded = unquote(url)
        assert "year=2023" in decoded
        assert "state='MT'" in decoded
        assert "state='MATO GROSSO'" in decoded
