from pathlib import Path

import numpy as np
import pytest
import requests
import xarray as xr

from dr_gcm.download_ecmwf_asia import load_domain
from dr_gcm.download_ecmwf_baseline import (
    configured_groups,
    lead_hours_for_group,
    parse_init,
    request_for_group,
    retrieve_group,
)


@pytest.fixture(scope="module")
def domain():
    return load_domain()


def test_default_groups_and_exact_lt7_boundary(domain):
    assert configured_groups(domain) == ["instantaneous", "accumulated"]
    instant = lead_hours_for_group(
        "2026-02", domain["download_groups"]["instantaneous"]
    )
    accumulated = lead_hours_for_group(
        "2026-02", domain["download_groups"]["accumulated"]
    )
    assert instant[:2] == ["0", "6"]
    assert instant[-1] == "5088"
    assert accumulated[:2] == ["24", "48"]
    assert accumulated[-1] == "5088"


def test_requests_do_not_mix_temporal_semantics(domain):
    instant = request_for_group("2026-02", domain, "instantaneous")
    accumulated = request_for_group("2026-02", domain, "accumulated")
    assert instant["data_format"] == "netcdf"
    assert instant["download_format"] == "unarchived"
    assert accumulated["data_format"] == "netcdf"
    assert accumulated["download_format"] == "unarchived"
    assert instant["variable"] == (
        domain["download_groups"]["instantaneous"]["variables"]
    )
    assert accumulated["variable"] == (
        domain["download_groups"]["accumulated"]["variables"]
    )
    soil = request_for_group("2026-02", domain, "soil_moisture")
    assert soil["variable"] == ["volumetric_soil_moisture"]
    assert soil["leadtime_hour"][0] == "24"
    assert soil["leadtime_hour"][-1] == "5088"


@pytest.mark.parametrize("value", ["2026-2", "2026-13", "bad"])
def test_init_must_be_strict(value):
    with pytest.raises(ValueError):
        parse_init(value)


def test_partial_is_validated_before_atomic_promotion(tmp_path):
    class Client:
        def retrieve(self, dataset, request, target):
            Path(target).write_text("download", encoding="utf-8")

    def validator(path, init, spec, leads):
        assert path.name.endswith(".nc.part")
        return {"ok": True}

    target = tmp_path / "group.nc"
    result = retrieve_group(
        Client(), "dataset", "2026-02", {},
        {"leadtime_hour": ["0", "6"]}, target, validator=validator
    )
    assert result["status"] == "downloaded"
    assert target.exists()
    assert not target.with_suffix(".nc.part").exists()


def test_existing_partial_is_not_silently_overwritten(tmp_path):
    target = tmp_path / "group.nc"
    target.with_suffix(".nc.part").write_text("partial", encoding="utf-8")
    with pytest.raises(FileExistsError):
        retrieve_group(
            object(), "dataset", "2026-02", {},
            {"leadtime_hour": ["0"]}, target, validator=lambda *args: {}
        )


def test_retry_partial_replaces_only_incomplete_file(tmp_path):
    class Client:
        def retrieve(self, dataset, request, target):
            Path(target).write_text("new", encoding="utf-8")

    target = tmp_path / "group.nc"
    partial = target.with_suffix(".nc.part")
    partial.write_text("old", encoding="utf-8")

    result = retrieve_group(
        Client(), "dataset", "2026-02", {},
        {"leadtime_hour": ["0"]}, target, retry_partial=True,
        validator=lambda *args: {"ok": True},
    )

    assert result["status"] == "downloaded"
    assert target.read_text(encoding="utf-8") == "new"


def test_expired_object_store_falls_back_to_resumable_variable_requests(tmp_path):
    source_to_output = {"source_a": "a", "source_b": "b"}

    class Client:
        def __init__(self):
            self.requests = []

        def retrieve(self, dataset, request, target):
            self.requests.append(list(request["variable"]))
            if len(request["variable"]) > 1:
                raise requests.HTTPError(
                    "403 Client Error: Forbidden for url: "
                    "https://object-store.example/expired.nc"
                )
            name = source_to_output[request["variable"][0]]
            xr.Dataset(
                {name: (("number", "forecast_period"), np.ones((2, 2)))},
                coords={"number": [0, 1], "forecast_period": [24, 48]},
            ).to_netcdf(target)

    def validator(path, init, spec, leads):
        with xr.open_dataset(path) as ds:
            expected = set(spec["expected_netcdf_variables"])
            assert expected <= set(ds.data_vars)
            return {"variables": sorted(expected)}

    client = Client()
    target = tmp_path / "group.nc"
    result = retrieve_group(
        client,
        "dataset",
        "2026-02",
        {"expected_netcdf_variables": ["a", "b"]},
        {"variable": ["source_a", "source_b"], "leadtime_hour": ["24", "48"]},
        target,
        retry_partial=True,
        validator=validator,
    )

    assert result["status"] == "downloaded_split_fallback"
    assert client.requests == [["source_a", "source_b"], ["source_a"], ["source_b"]]
    with xr.open_dataset(target) as ds:
        assert set(ds.data_vars) == {"a", "b"}
    assert not (tmp_path / "group.nc.split-parts").exists()


def test_non_object_store_403_does_not_trigger_split_fallback(tmp_path):
    class Client:
        def retrieve(self, dataset, request, target):
            raise requests.HTTPError(
                "403 Client Error: Forbidden for url: https://example.test/api"
            )

    with pytest.raises(requests.HTTPError):
        retrieve_group(
            Client(),
            "dataset",
            "2026-02",
            {"expected_netcdf_variables": ["a", "b"]},
            {"variable": ["source_a", "source_b"], "leadtime_hour": ["24"]},
            tmp_path / "group.nc",
            retry_partial=True,
            validator=lambda *args: {},
        )
