"""Tests for the Tyko client."""

from unittest.mock import patch

import pytest
from pytest_httpx import HTTPXMock

from tyko import Experiment, Project, Run
from tyko.client import TykoClient


@pytest.fixture(autouse=True)
def default_api_key():
    """Set default environment variables for tests."""
    with patch.dict("os.environ", {"TYKO_API_KEY": "test-api-key"}):
        yield "test-api-key"


def test_client_uses_default_endpoint(default_api_key: str):
    """Test that the client uses the default API endpoint."""
    client = TykoClient()
    assert client.api_url == "https://api.tyko-labs.com"
    assert client.api_key == default_api_key


def test_client_uses_env_vars():
    """Test that the client uses environment variables for configuration."""
    with patch.dict("os.environ", {"TYKO_API_URL": "http://localhost:8000"}):
        client = TykoClient()
        assert client.api_url == "http://localhost:8000"


def test_start_run_with_project(httpx_mock: HTTPXMock):
    """Test starting a run with just a project name."""

    httpx_mock.add_response(
        method="POST",
        url="https://api.tyko-labs.com/runs",
        json={
            "id": "run-id-1234",
            "project": {"name": "my-project"},
            "experiment": {"name": "default"},
            "name": "run-1234",
            "sequential": 1,
        },
    )
    httpx_mock.add_response(
        method="PATCH",
        url="https://api.tyko-labs.com/runs/run-id-1234",
        json={},
    )

    client = TykoClient()

    with client.start_run(project="my-project") as run:
        assert isinstance(run, Run)
        assert isinstance(run.experiment, Experiment)
        assert isinstance(run.project, Project)
        assert run.project.name == "my-project"
        assert run.experiment.name == "default"
        assert run.name == "run-1234"

    requests = httpx_mock.get_requests()

    assert len(requests) == 2
    assert requests[0].url == "https://api.tyko-labs.com/runs"
    assert requests[0].headers["Authorization"] == "Bearer test-api-key"


def test_run_closes_with_completed_status(httpx_mock: HTTPXMock):
    """Test that a run sends 'completed' status when exiting without exception."""
    httpx_mock.add_response(
        method="POST",
        url="https://api.tyko-labs.com/runs",
        json={
            "id": "run-id-5678",
            "project": {"name": "test-project"},
            "experiment": {"name": "default"},
            "name": "test-run",
            "sequential": 1,
        },
    )
    httpx_mock.add_response(
        method="PATCH",
        url="https://api.tyko-labs.com/runs/run-id-5678",
        json={},
    )

    client = TykoClient()

    with client.start_run(project="test-project"):
        pass

    requests = httpx_mock.get_requests()
    patch_request = requests[1]

    assert patch_request.method == "PATCH"
    assert patch_request.url == "https://api.tyko-labs.com/runs/run-id-5678"

    import json

    body = json.loads(patch_request.content)
    assert body["status"] == "completed"
    assert "at" in body


def test_run_closes_with_failed_status_on_exception(httpx_mock: HTTPXMock):
    """Test that a run sends 'failed' status when exiting with an exception."""
    httpx_mock.add_response(
        method="POST",
        url="https://api.tyko-labs.com/runs",
        json={
            "id": "run-id-9999",
            "project": {"name": "test-project"},
            "experiment": {"name": "default"},
            "name": "failed-run",
            "sequential": 2,
        },
    )
    httpx_mock.add_response(
        method="PATCH",
        url="https://api.tyko-labs.com/runs/run-id-9999",
        json={},
    )

    client = TykoClient()

    with pytest.raises(ValueError):
        with client.start_run(project="test-project"):
            raise ValueError("Something went wrong")

    requests = httpx_mock.get_requests()
    patch_request = requests[1]

    assert patch_request.method == "PATCH"
    assert patch_request.url == "https://api.tyko-labs.com/runs/run-id-9999"

    import json

    body = json.loads(patch_request.content)
    assert body["status"] == "failed"
    assert "at" in body


def test_run_params_update(httpx_mock: HTTPXMock):
    """Test setting multiple config values with update()."""
    httpx_mock.add_response(
        method="POST",
        url="https://api.tyko-labs.com/runs",
        json={
            "id": "run-config-test",
            "project": {"name": "test-project"},
            "experiment": {"name": "default"},
            "name": "config-run",
            "sequential": 1,
        },
    )
    httpx_mock.add_response(
        method="PATCH",
        url="https://api.tyko-labs.com/runs/run-config-test",
        json={},
    )
    httpx_mock.add_response(
        method="PATCH",
        url="https://api.tyko-labs.com/runs/run-config-test",
        json={},
    )

    client = TykoClient()

    with client.start_run(project="test-project") as run:
        run.params.update({"learning_rate": 0.001, "batch_size": 32})

    requests = httpx_mock.get_requests()

    # First request is POST to create run
    assert requests[0].method == "POST"

    # Second request is PATCH to set config
    assert requests[1].method == "PATCH"
    assert requests[1].url == "https://api.tyko-labs.com/runs/run-config-test"

    import json

    config_body = json.loads(requests[1].content)
    assert config_body["params"] == {"learning_rate": 0.001, "batch_size": 32}

    # Third request is PATCH to close run
    assert requests[2].method == "PATCH"
    close_body = json.loads(requests[2].content)
    assert close_body["status"] == "completed"


def test_run_params_setitem(httpx_mock: HTTPXMock):
    """Test setting config values with dict-style access."""
    httpx_mock.add_response(
        method="POST",
        url="https://api.tyko-labs.com/runs",
        json={
            "id": "run-setitem-test",
            "project": {"name": "test-project"},
            "experiment": {"name": "default"},
            "name": "setitem-run",
            "sequential": 1,
        },
    )
    # Two PATCH requests for two config sets + one for close
    httpx_mock.add_response(
        method="PATCH",
        url="https://api.tyko-labs.com/runs/run-setitem-test",
        json={},
    )
    httpx_mock.add_response(
        method="PATCH",
        url="https://api.tyko-labs.com/runs/run-setitem-test",
        json={},
    )
    httpx_mock.add_response(
        method="PATCH",
        url="https://api.tyko-labs.com/runs/run-setitem-test",
        json={},
    )

    client = TykoClient()

    with client.start_run(project="test-project") as run:
        run.params["learning_rate"] = 0.001
        run.params["batch_size"] = 32

    requests = httpx_mock.get_requests()

    import json

    # Second request sets learning_rate
    config_body_1 = json.loads(requests[1].content)
    assert config_body_1["params"] == {"learning_rate": 0.001}

    # Third request sets batch_size
    config_body_2 = json.loads(requests[2].content)
    assert config_body_2["params"] == {"batch_size": 32}


def test_run_params_with_various_types(httpx_mock: HTTPXMock):
    """Test setting config with bool, int, float, and string values."""
    httpx_mock.add_response(
        method="POST",
        url="https://api.tyko-labs.com/runs",
        json={
            "id": "run-types-test",
            "project": {"name": "test-project"},
            "experiment": {"name": "default"},
            "name": "types-run",
            "sequential": 1,
        },
    )
    httpx_mock.add_response(
        method="PATCH",
        url="https://api.tyko-labs.com/runs/run-types-test",
        json={},
    )
    httpx_mock.add_response(
        method="PATCH",
        url="https://api.tyko-labs.com/runs/run-types-test",
        json={},
    )

    client = TykoClient()

    config: dict[str, object] = {
        "enabled": True,
        "epochs": 100,
        "learning_rate": 0.001,
        "model_name": "transformer",
    }

    with client.start_run(project="test-project") as run:
        run.params.update(config)

    requests = httpx_mock.get_requests()

    import json

    config_body = json.loads(requests[1].content)
    assert config_body["params"]["enabled"] is True
    assert config_body["params"]["epochs"] == 100
    assert config_body["params"]["learning_rate"] == 0.001
    assert config_body["params"]["model_name"] == "transformer"


def test_run_params_local_cache(httpx_mock: HTTPXMock):
    """Test that config values are cached locally for reading."""
    httpx_mock.add_response(
        method="POST",
        url="https://api.tyko-labs.com/runs",
        json={
            "id": "run-cache-test",
            "project": {"name": "test-project"},
            "experiment": {"name": "default"},
            "name": "cache-run",
            "sequential": 1,
        },
    )
    httpx_mock.add_response(
        method="PATCH",
        url="https://api.tyko-labs.com/runs/run-cache-test",
        json={},
    )
    httpx_mock.add_response(
        method="PATCH",
        url="https://api.tyko-labs.com/runs/run-cache-test",
        json={},
    )

    client = TykoClient()

    with client.start_run(project="test-project") as run:
        run.params["learning_rate"] = 0.001
        # Should be able to read back the value from local cache
        assert run.params["learning_rate"] == 0.001
        assert "learning_rate" in run.params
        assert len(run.params) == 1


# def test_start_run_with_experiment():
#     """Test starting a run with project and experiment names."""
#     client = TykoClient()
#     with client.start_run(project="my-project", experiment="baseline") as run:
#         assert run.project.name == "my-project"
#         assert run.experiment.name == "baseline"


# def test_start_run_with_custom_name():
#     """Test starting a run with a custom run name."""
#     client = TykoClient()
#     with client.start_run(project="my-project", name="run-v1") as run:
#         assert run.name == "run-v1"


# def test_run_generates_random_name():
#     """Test that runs generate random names when not provided."""
#     client = TykoClient()
#     with client.start_run(project="my-project") as run:
#         assert run.name is not None
#         assert len(run.name) > 0
