import base64
from pathlib import Path

import pytest

from hermes_cli import web_server

pytest.importorskip("starlette.testclient")
from starlette.testclient import TestClient


@pytest.fixture
def client(monkeypatch):
    previous_auth_required = getattr(web_server.app.state, "auth_required", None)
    web_server.app.state.auth_required = False
    test_client = TestClient(web_server.app)
    test_client.headers[web_server._SESSION_HEADER_NAME] = web_server._SESSION_TOKEN
    try:
        yield test_client
    finally:
        if previous_auth_required is None:
            try:
                delattr(web_server.app.state, "auth_required")
            except AttributeError:
                pass
        else:
            web_server.app.state.auth_required = previous_auth_required


def test_fs_list_sorts_and_hides_noise(client, tmp_path):
    root = tmp_path / "project"
    root.mkdir()
    (root / "b.txt").write_text("b")
    (root / "a_dir").mkdir()
    (root / "a.txt").write_text("a")
    (root / "node_modules").mkdir()
    (root / ".git").mkdir()

    response = client.get("/api/fs/list", params={"path": str(root)})

    assert response.status_code == 200
    entries = response.json()["entries"]
    assert [entry["name"] for entry in entries] == ["a_dir", "a.txt", "b.txt"]
    assert entries[0] == {"name": "a_dir", "path": str(root / "a_dir"), "isDirectory": True}
    assert all(entry["name"] not in {".git", "node_modules"} for entry in entries)


def test_fs_list_accepts_relative_paths(client, tmp_path, monkeypatch):
    monkeypatch.chdir(tmp_path)
    (tmp_path / "rel").mkdir()
    (tmp_path / "rel" / "file.txt").write_text("ok")

    response = client.get("/api/fs/list", params={"path": "rel"})

    assert response.status_code == 200
    assert response.json()["entries"] == [
        {"name": "file.txt", "path": str(tmp_path / "rel" / "file.txt"), "isDirectory": False}
    ]


def test_fs_list_missing_path_returns_structured_error(client, tmp_path):
    response = client.get("/api/fs/list", params={"path": str(tmp_path / "missing")})

    assert response.status_code == 200
    assert response.json() == {"entries": [], "error": "ENOENT"}


def test_fs_read_text_matches_preview_shape_and_truncates(client, tmp_path, monkeypatch):
    monkeypatch.setattr(web_server, "_FS_TEXT_SOURCE_MAX_BYTES", 32)
    monkeypatch.setattr(web_server, "_FS_TEXT_PREVIEW_MAX_BYTES", 5)
    target = tmp_path / "sample.py"
    target.write_text("print('hello')")

    response = client.get("/api/fs/read-text", params={"path": str(target)})

    assert response.status_code == 200
    assert response.json() == {
        "binary": False,
        "byteSize": 14,
        "language": "python",
        "mimeType": "text/x-python",
        "path": str(target),
        "text": "print",
        "truncated": True,
    }


def test_fs_read_text_rejects_source_over_cap(client, tmp_path, monkeypatch):
    monkeypatch.setattr(web_server, "_FS_TEXT_SOURCE_MAX_BYTES", 4)
    target = tmp_path / "large.txt"
    target.write_text("12345")

    response = client.get("/api/fs/read-text", params={"path": str(target)})

    assert response.status_code == 413


def test_fs_read_text_flags_binary(client, tmp_path):
    target = tmp_path / "blob.bin"
    target.write_bytes(b"hello\x00world")

    response = client.get("/api/fs/read-text", params={"path": str(target)})

    assert response.status_code == 200
    body = response.json()
    assert body["binary"] is True
    assert body["text"].startswith("hello")


def test_fs_read_data_url_returns_capped_data_url(client, tmp_path, monkeypatch):
    monkeypatch.setattr(web_server, "_FS_DATA_URL_MAX_BYTES", 16)
    target = tmp_path / "image.png"
    target.write_bytes(b"pngbytes")

    response = client.get("/api/fs/read-data-url", params={"path": str(target)})

    assert response.status_code == 200
    assert response.json() == {"dataUrl": "data:image/png;base64," + base64.b64encode(b"pngbytes").decode("ascii")}


def test_fs_read_data_url_rejects_over_cap(client, tmp_path, monkeypatch):
    monkeypatch.setattr(web_server, "_FS_DATA_URL_MAX_BYTES", 3)
    target = tmp_path / "image.png"
    target.write_bytes(b"1234")

    response = client.get("/api/fs/read-data-url", params={"path": str(target)})

    assert response.status_code == 413


def test_fs_git_root_for_nested_file(client, tmp_path):
    (tmp_path / ".git").mkdir()
    nested = tmp_path / "pkg" / "mod"
    nested.mkdir(parents=True)
    target = nested / "file.py"
    target.write_text("x")

    response = client.get("/api/fs/git-root", params={"path": str(target)})

    assert response.status_code == 200
    assert response.json() == {"root": str(tmp_path)}


def test_fs_git_root_returns_null_outside_repo(client, tmp_path):
    response = client.get("/api/fs/git-root", params={"path": str(tmp_path)})

    assert response.status_code == 200
    assert response.json() == {"root": None}


def test_fs_default_cwd_prefers_existing_terminal_cwd(client, tmp_path, monkeypatch):
    monkeypatch.setattr(web_server, "load_config", lambda: {"terminal": {"cwd": str(tmp_path)}})
    monkeypatch.setenv("TERMINAL_CWD", str(tmp_path / "env"))
    monkeypatch.setattr(web_server.Path, "cwd", lambda: tmp_path / "process")
    monkeypatch.setattr(web_server, "_fs_git_branch", lambda cwd: "main")

    response = client.get("/api/fs/default-cwd")

    assert response.status_code == 200
    assert response.json() == {"cwd": str(tmp_path), "branch": "main"}


def test_fs_default_cwd_falls_back_when_terminal_cwd_is_invalid(client, tmp_path, monkeypatch):
    fallback = tmp_path / "backend"
    fallback.mkdir()
    monkeypatch.setattr(web_server, "load_config", lambda: {"terminal": {"cwd": "/client/missing"}})
    monkeypatch.setenv("TERMINAL_CWD", "/client/missing")
    monkeypatch.setattr(web_server.Path, "cwd", lambda: fallback)
    monkeypatch.setattr(web_server, "_fs_git_branch", lambda cwd: "")

    response = client.get("/api/fs/default-cwd")

    assert response.status_code == 200
    assert response.json() == {"cwd": str(fallback), "branch": ""}


def test_fs_endpoints_require_auth(tmp_path):
    client = TestClient(web_server.app)
    target = tmp_path / "secret.txt"
    target.write_text("secret")

    list_response = client.get("/api/fs/list", params={"path": str(tmp_path)})
    read_response = client.get("/api/fs/read-text", params={"path": str(target)})
    default_response = client.get("/api/fs/default-cwd")

    assert list_response.status_code == 401
    assert read_response.status_code == 401
    assert default_response.status_code == 401
