"""Цвет-метка, сайт (0.63), булавка (0.66), репозиторий (0.67): валидация, очистка."""

from fastapi.testclient import TestClient


def test_color_and_site_roundtrip(client: TestClient) -> None:
    resp = client.post(
        "/api/projects",
        json={
            "name": "Метка",
            "color": "#7AA2F7",
            "site_url": "https://example.com",
            "repository_url": "https://git.example.com/owner/repo",
        },
    )
    assert resp.status_code == 200
    project = resp.json()
    # цвет нормализуется в нижний регистр — с ним сравнивается палитра фронта
    assert project["color"] == "#7aa2f7"
    assert project["site_url"] == "https://example.com"
    assert project["repository_url"] == "https://git.example.com/owner/repo"
    # поля видны и в одиночном чтении
    single = client.get(f"/api/projects/{project['id']}").json()
    assert single["color"] == "#7aa2f7"
    assert single["site_url"] == "https://example.com"
    assert single["repository_url"] == "https://git.example.com/owner/repo"


def test_project_without_color_and_site(client: TestClient) -> None:
    project = client.post("/api/projects", json={"name": "Пусто"}).json()
    # поля необязательные: по умолчанию метки, сайта и репозитория нет
    assert project["color"] is None
    assert project["site_url"] is None
    assert project["repository_url"] is None


def test_patch_sets_and_clears(client: TestClient) -> None:
    project_id = client.post("/api/projects", json={"name": "Правки"}).json()["id"]
    patched = client.patch(
        f"/api/projects/{project_id}",
        json={
            "color": "#9ECE6A",
            "site_url": "https://example.org/page",
            "repository_url": "https://git.example.org/team/repo.git",
        },
    ).json()
    assert patched["color"] == "#9ece6a"
    assert patched["site_url"] == "https://example.org/page"
    assert patched["repository_url"] == "https://git.example.org/team/repo.git"
    # пустая строка — «убрать» (и через PATCH, и через MCP)
    cleared = client.patch(
        f"/api/projects/{project_id}", json={"color": "", "site_url": "", "repository_url": ""}
    ).json()
    assert cleared["color"] is None
    assert cleared["site_url"] is None
    assert cleared["repository_url"] is None
    # явный null тоже очищает
    again = client.patch(f"/api/projects/{project_id}", json={"color": "#ff9e64"}).json()
    assert again["color"] == "#ff9e64"
    assert client.patch(f"/api/projects/{project_id}", json={"color": None}).json()["color"] is None
    # PATCH без репозитория его не трогает
    repo = client.patch(
        f"/api/projects/{project_id}", json={"repository_url": "https://git.example.org/a/b"}
    ).json()
    assert repo["repository_url"] == "https://git.example.org/a/b"
    renamed = client.patch(f"/api/projects/{project_id}", json={"name": "Правки-2"}).json()
    assert renamed["repository_url"] == "https://git.example.org/a/b"


def test_invalid_color_rejected(client: TestClient) -> None:
    project_id = client.post("/api/projects", json={"name": "Цвет"}).json()["id"]
    for bad in ("#GGGGGG", "#7aa2f", "7aa2f7", "red"):
        resp = client.patch(f"/api/projects/{project_id}", json={"color": bad})
        assert resp.status_code == 422, bad


def test_invalid_site_url_rejected(client: TestClient) -> None:
    project_id = client.post("/api/projects", json={"name": "Сайт"}).json()["id"]
    for bad in ("ftp://example.com", "javascript:alert(1)", "example.com", "https://"):
        resp = client.patch(f"/api/projects/{project_id}", json={"site_url": bad})
        assert resp.status_code == 422, bad


def test_invalid_repository_url_rejected(client: TestClient) -> None:
    """Репозиторий (0.67) валидируется как сайт: только http(s) с хостом."""
    project_id = client.post("/api/projects", json={"name": "Репозиторий"}).json()["id"]
    for bad in ("git@github.com:owner/repo.git", "ftp://git.example.com/a/b", "/owner/repo", ""):
        resp = client.patch(f"/api/projects/{project_id}", json={"repository_url": bad})
        # пустая строка — не ошибка, а очистка: проверяем остальные
        assert resp.status_code == (200 if bad == "" else 422), bad


def test_task_carries_project_color(client: TestClient) -> None:
    project_id = client.post(
        "/api/projects", json={"name": "С меткой", "color": "#bb9af7"}
    ).json()["id"]
    task_id = client.post("/api/tasks", json={"title": "Задача"}).json()["id"]
    client.patch(f"/api/tasks/{task_id}", json={"project_id": project_id})
    # ProjectOut вложен в TaskOut — метка проекта доезжает до задач без правок схемы
    task = next(t for t in client.get("/api/tasks").json() if t["project"])
    assert task["project"]["color"] == "#bb9af7"


def test_pinned_default_and_toggle(client: TestClient) -> None:
    """Булавка проекта (0.66): по умолчанию снята, PATCH ставит и снимает."""
    project = client.post("/api/projects", json={"name": "Булавка"}).json()
    assert project["pinned"] is False
    project_id = project["id"]

    pinned = client.patch(f"/api/projects/{project_id}", json={"pinned": True}).json()
    assert pinned["pinned"] is True
    # в одиночном чтении и в списке — тоже закреплён
    assert client.get(f"/api/projects/{project_id}").json()["pinned"] is True
    listed = next(p for p in client.get("/api/projects").json() if p["id"] == project_id)
    assert listed["pinned"] is True

    # PATCH без pinned (частичное обновление) булавку не трогает
    renamed = client.patch(f"/api/projects/{project_id}", json={"name": "Булавка-2"}).json()
    assert renamed["pinned"] is True

    unpinned = client.patch(f"/api/projects/{project_id}", json={"pinned": False}).json()
    assert unpinned["pinned"] is False
