from __future__ import annotations

import io
import json
import urllib.error
import urllib.request

import pytest

from recoil.execution.providers import flora_projects
from recoil.pipeline.tools import flora_canvas_sync


PROJECT = "fixture"


@pytest.fixture(autouse=True)
def _projects_root(tmp_path, monkeypatch):
    root = tmp_path / "projects"
    root.mkdir()
    (root / ".recoil-data-root").touch()
    (root / PROJECT).mkdir()
    monkeypatch.setenv("RECOIL_PROJECTS_ROOT", str(root))
    return root / PROJECT


class _Resp:
    def __init__(self, body: bytes = b"{}"):
        self._body = body

    def __enter__(self):
        return self

    def __exit__(self, *_exc):
        return False

    def read(self) -> bytes:
        return self._body


def _http_error(url: str, code: int) -> urllib.error.HTTPError:
    return urllib.error.HTTPError(
        url,
        code,
        "error",
        hdrs={},
        fp=io.BytesIO(f"err-{code}".encode()),
    )


def test_mapped_episode_short_circuits_without_create(monkeypatch):
    mapping = flora_projects._projects_path(PROJECT)
    mapping.parent.mkdir(parents=True, exist_ok=True)
    mapping.write_text(
        json.dumps({"ep_001": {"project_id": "prj_existing"}}),
        encoding="utf-8",
    )

    def _fail_urlopen(*_args, **_kwargs):
        raise AssertionError("project creation should not be called")

    monkeypatch.setattr(urllib.request, "urlopen", _fail_urlopen)

    assert (
        flora_projects.resolve_flora_project(
            PROJECT,
            1,
            api_key="ak_test",
            workspace_id="ws_test",
        )
        == "prj_existing"
    )


def test_unmapped_episode_creates_once_then_reuses(monkeypatch):
    calls: list[urllib.request.Request] = []

    def _urlopen(req, timeout=30):
        calls.append(req)
        assert req.full_url.endswith("/projects")
        assert req.get_method() == "POST"
        body = json.loads(req.data.decode())
        assert body == {
            "workspace_id": "ws_test",
            "name": "recoil-fixture-ep001",
        }
        assert req.headers["User-agent"] == "recoil-pipeline/1.0"
        return _Resp(json.dumps({"project_id": "prj_created"}).encode())

    monkeypatch.setattr(urllib.request, "urlopen", _urlopen)

    assert (
        flora_projects.resolve_flora_project(
            PROJECT,
            1,
            api_key="ak_test",
            workspace_id="ws_test",
        )
        == "prj_created"
    )
    assert (
        flora_projects.resolve_flora_project(
            PROJECT,
            1,
            api_key="ak_test",
            workspace_id="ws_test",
        )
        == "prj_created"
    )
    assert len(calls) == 1


def test_creation_failure_falls_back_to_env_and_warns(monkeypatch, caplog):
    monkeypatch.setenv("RECOIL_FLORA_PROJECT", "prj_legacy")

    def _urlopen(req, timeout=30):
        raise _http_error(req.full_url, 500)

    monkeypatch.setattr(urllib.request, "urlopen", _urlopen)

    assert (
        flora_projects.resolve_flora_project(
            PROJECT,
            2,
            api_key="ak_test",
            workspace_id="ws_test",
        )
        == "prj_legacy"
    )
    assert "fell back to RECOIL_FLORA_PROJECT" in caplog.text


def test_canvas_sync_default_uses_resolver_without_create(monkeypatch):
    seen: dict[str, object] = {}
    monkeypatch.setenv("FLORA_API_KEY", "ak_test")
    monkeypatch.setenv("RECOIL_FLORA_WORKSPACE", "ws_test")

    def _resolve(project, episode, *, api_key, workspace_id, create=True):
        seen.update(
            {
                "project": project,
                "episode": episode,
                "api_key": api_key,
                "workspace_id": workspace_id,
                "create": create,
            }
        )
        return "prj_mapped"

    monkeypatch.setattr(flora_canvas_sync, "resolve_flora_project", _resolve)
    monkeypatch.setattr(flora_canvas_sync, "_upload_video_signed_url", lambda **_kw: "asset_1")

    result = flora_canvas_sync.attach_video_to_canvas(
        b"video",
        "take.mp4",
        "EP001_PASS",
        project=PROJECT,
        episode=1,
    )

    assert result["success"] is True
    assert result["flora_project_id"] == "prj_mapped"
    assert seen["create"] is False


def test_mapping_file_persists_across_calls(monkeypatch):
    monkeypatch.setattr(
        urllib.request,
        "urlopen",
        lambda *_args, **_kwargs: _Resp(json.dumps({"id": "prj_persisted"}).encode()),
    )

    assert (
        flora_projects.resolve_flora_project(
            PROJECT,
            3,
            api_key="ak_test",
            workspace_id="ws_test",
        )
        == "prj_persisted"
    )

    mapping = flora_projects._cache_read(flora_projects._projects_path(PROJECT))
    assert mapping["ep_003"]["project_id"] == "prj_persisted"
    assert "created_at" in mapping["ep_003"]

    def _fail_urlopen(*_args, **_kwargs):
        raise AssertionError("persisted mapping should be reused")

    monkeypatch.setattr(urllib.request, "urlopen", _fail_urlopen)
    assert (
        flora_projects.resolve_flora_project(
            PROJECT,
            3,
            api_key="ak_test",
            workspace_id="ws_test",
        )
        == "prj_persisted"
    )
