"""Theme & layout API tests — Phase 11.3 (versioned themes/layouts)."""
from __future__ import annotations

import pytest

from app.events.publisher import get_event_publisher
from app.events.types import ExperienceEventType
from app.tests.conftest import TENANT_A, TENANT_B, tenant_headers


async def _workspace(client, code="ws-theme"):
    resp = await client.post(
        "/api/v1/workspaces",
        json={"code": code, "name": "Workspace", "status": "active"},
        headers=tenant_headers(TENANT_A),
    )
    assert resp.status_code == 201, resp.text
    return resp.json()


async def _site_page(client, workspace_id: str, code="main"):
    site = await client.post(
        "/api/v1/sites",
        json={"workspace_id": workspace_id, "code": code, "name": "Main"},
        headers=tenant_headers(TENANT_A),
    )
    assert site.status_code == 201, site.text
    page = await client.post(
        "/api/v1/pages",
        json={
            "workspace_id": workspace_id,
            "site_id": site.json()["id"],
            "code": "home",
            "slug": "home",
            "title": "Home",
            "page_type": "landing",
        },
        headers=tenant_headers(TENANT_A),
    )
    assert page.status_code == 201, page.text
    return site.json(), page.json()


@pytest.mark.asyncio
async def test_health_phase_113(client):
    resp = await client.get("/health")
    assert resp.status_code == 200
    # Health version tracks current service version (forward-compatible).
    assert resp.json()["version"].startswith("0.11.")

    caps = await client.get("/capabilities")
    body = caps.json()
    # Phase 11.3 (themes/layouts) capabilities remain true regardless of the
    # currently active phase (forward-compatible with later phases).
    assert body["features"]["themes"] is True
    assert body["features"]["theme_versions"] is True
    assert body["features"]["layouts"] is True
    assert body["features"]["layout_versions"] is True
    assert body["features"]["directionality_ready_shells"] is True
    assert "both" in body["direction_modes"]


@pytest.mark.asyncio
async def test_theme_layout_versioned_flow(client):
    ws = await _workspace(client)
    workspace_id = ws["id"]
    site, page = await _site_page(client, workspace_id)

    # --- Theme catalog entry (draft) ---
    theme = await client.post(
        "/api/v1/themes",
        json={
            "workspace_id": workspace_id,
            "code": "brand-a",
            "name": "Brand A",
            "direction_mode": "rtl",
        },
        headers=tenant_headers(TENANT_A),
    )
    assert theme.status_code == 201, theme.text
    theme_id = theme.json()["id"]
    assert theme.json()["status"] == "draft"

    # --- Theme version (draft) with tokens ---
    version = await client.post(
        "/api/v1/theme-versions",
        json={
            "workspace_id": workspace_id,
            "theme_id": theme_id,
            "tokens": {
                "--color-primary": "#0d6efd",
                "--font-body": "Vazirmatn",
            },
            "brand_shell": {"logo_slot": "header.logo"},
            "direction_mode": "rtl",
        },
        headers=tenant_headers(TENANT_A),
    )
    assert version.status_code == 201, version.text
    version_id = version.json()["id"]
    assert version.json()["status"] == "draft"
    assert version.json()["version_number"] == 1

    # Cannot bind a site to a draft (unpublished) theme/version
    blocked = await client.post(
        "/api/v1/site-theme-bindings",
        json={
            "workspace_id": workspace_id,
            "site_id": site["id"],
            "theme_id": theme_id,
            "theme_version_id": version_id,
        },
        headers=tenant_headers(TENANT_A),
    )
    assert blocked.status_code == 409

    # --- Publish theme version — theme auto-activates from draft ---
    published = await client.post(
        f"/api/v1/theme-versions/{version_id}/publish",
        headers=tenant_headers(TENANT_A),
    )
    assert published.status_code == 200, published.text
    assert published.json()["status"] == "published"
    assert published.json()["is_immutable"] is True

    theme_after_publish = await client.get(
        f"/api/v1/themes/{theme_id}", headers=tenant_headers(TENANT_A)
    )
    assert theme_after_publish.json()["status"] == "active"
    assert theme_after_publish.json()["published_version_id"] == version_id

    # Idempotent explicit activation (already active)
    activated = await client.post(
        f"/api/v1/themes/{theme_id}/activate",
        headers=tenant_headers(TENANT_A),
    )
    assert activated.status_code == 200
    assert activated.json()["status"] == "active"

    # --- Bind theme version to site ---
    binding = await client.post(
        "/api/v1/site-theme-bindings",
        json={
            "workspace_id": workspace_id,
            "site_id": site["id"],
            "theme_id": theme_id,
            "theme_version_id": version_id,
        },
        headers=tenant_headers(TENANT_A),
    )
    assert binding.status_code == 201, binding.text
    binding_id = binding.json()["id"]
    assert binding.json()["is_active"] is True

    # --- Layout catalog entry + version ---
    layout = await client.post(
        "/api/v1/layouts",
        json={
            "workspace_id": workspace_id,
            "code": "landing-shell",
            "name": "Landing Shell",
            "direction_mode": "both",
            "kind": "page_shell",
        },
        headers=tenant_headers(TENANT_A),
    )
    assert layout.status_code == 201, layout.text
    layout_id = layout.json()["id"]

    layout_version = await client.post(
        "/api/v1/layout-versions",
        json={
            "workspace_id": workspace_id,
            "layout_id": layout_id,
            "regions": [
                {"key": "header", "role": "header"},
                {"key": "main", "role": "main"},
                {"key": "footer", "role": "footer"},
            ],
            "structure_shell": {"grid": "single-column"},
        },
        headers=tenant_headers(TENANT_A),
    )
    assert layout_version.status_code == 201, layout_version.text
    layout_version_id = layout_version.json()["id"]

    layout_version_pub = await client.post(
        f"/api/v1/layout-versions/{layout_version_id}/publish",
        headers=tenant_headers(TENANT_A),
    )
    assert layout_version_pub.status_code == 200, layout_version_pub.text

    listed = await client.get(
        f"/api/v1/layouts?workspace_id={workspace_id}&q=landing&sort_by=name",
        headers=tenant_headers(TENANT_A),
    )
    assert listed.status_code == 200
    assert listed.json()["total"] >= 1

    # --- Bind layout version to page ---
    page_binding = await client.post(
        "/api/v1/page-layout-bindings",
        json={
            "workspace_id": workspace_id,
            "site_id": site["id"],
            "page_id": page["id"],
            "layout_id": layout_id,
            "layout_version_id": layout_version_id,
        },
        headers=tenant_headers(TENANT_A),
    )
    assert page_binding.status_code == 201, page_binding.text
    page_binding_id = page_binding.json()["id"]

    # Soft-delete blocked while theme/version is actively bound
    del_theme = await client.post(
        f"/api/v1/themes/{theme_id}/delete",
        headers=tenant_headers(TENANT_A),
    )
    assert del_theme.status_code == 409

    retire_blocked = await client.post(
        f"/api/v1/theme-versions/{version_id}/retire",
        headers=tenant_headers(TENANT_A),
    )
    assert retire_blocked.status_code == 409

    # --- Unassign bindings ---
    unassigned_theme = await client.post(
        f"/api/v1/site-theme-bindings/{binding_id}/unassign",
        headers=tenant_headers(TENANT_A),
    )
    assert unassigned_theme.status_code == 200
    assert unassigned_theme.json()["is_deleted"] is True
    assert unassigned_theme.json()["is_active"] is False

    unassigned_layout = await client.post(
        f"/api/v1/page-layout-bindings/{page_binding_id}/unassign",
        headers=tenant_headers(TENANT_A),
    )
    assert unassigned_layout.status_code == 200
    assert unassigned_layout.json()["is_active"] is False

    archived = await client.post(
        f"/api/v1/themes/{theme_id}/archive",
        headers=tenant_headers(TENANT_A),
    )
    assert archived.status_code == 200
    assert archived.json()["status"] == "archived"

    events = {e.event_type for e in get_event_publisher().published}
    assert ExperienceEventType.THEME_CREATED.value in events
    assert ExperienceEventType.THEME_VERSION_CREATED.value in events
    assert ExperienceEventType.THEME_VERSION_PUBLISHED.value in events
    assert ExperienceEventType.THEME_ACTIVATED.value in events
    assert ExperienceEventType.SITE_THEME_ASSIGNED.value in events
    assert ExperienceEventType.SITE_THEME_UNASSIGNED.value in events
    assert ExperienceEventType.LAYOUT_CREATED.value in events
    assert ExperienceEventType.LAYOUT_VERSION_CREATED.value in events
    assert ExperienceEventType.LAYOUT_VERSION_PUBLISHED.value in events
    assert ExperienceEventType.PAGE_LAYOUT_ASSIGNED.value in events
    assert ExperienceEventType.PAGE_LAYOUT_UNASSIGNED.value in events
    assert ExperienceEventType.THEME_ARCHIVED.value in events


@pytest.mark.asyncio
async def test_site_theme_binding_replaces_previous(client):
    ws = await _workspace(client, code="ws-replace")
    workspace_id = ws["id"]
    site, _page = await _site_page(client, workspace_id, code="replace-site")

    theme = await client.post(
        "/api/v1/themes",
        json={"workspace_id": workspace_id, "code": "t1", "name": "T1"},
        headers=tenant_headers(TENANT_A),
    )
    theme_id = theme.json()["id"]

    v1 = await client.post(
        "/api/v1/theme-versions",
        json={
            "workspace_id": workspace_id,
            "theme_id": theme_id,
            "tokens": {"--color": "#111"},
        },
        headers=tenant_headers(TENANT_A),
    )
    v1_id = v1.json()["id"]
    await client.post(
        f"/api/v1/theme-versions/{v1_id}/publish", headers=tenant_headers(TENANT_A)
    )

    b1 = await client.post(
        "/api/v1/site-theme-bindings",
        json={
            "workspace_id": workspace_id,
            "site_id": site["id"],
            "theme_id": theme_id,
            "theme_version_id": v1_id,
        },
        headers=tenant_headers(TENANT_A),
    )
    assert b1.status_code == 201

    v2 = await client.post(
        "/api/v1/theme-versions",
        json={
            "workspace_id": workspace_id,
            "theme_id": theme_id,
            "tokens": {"--color": "#222"},
        },
        headers=tenant_headers(TENANT_A),
    )
    v2_id = v2.json()["id"]
    await client.post(
        f"/api/v1/theme-versions/{v2_id}/publish", headers=tenant_headers(TENANT_A)
    )

    b2 = await client.post(
        "/api/v1/site-theme-bindings",
        json={
            "workspace_id": workspace_id,
            "site_id": site["id"],
            "theme_id": theme_id,
            "theme_version_id": v2_id,
        },
        headers=tenant_headers(TENANT_A),
    )
    assert b2.status_code == 201

    events = {e.event_type for e in get_event_publisher().published}
    assert ExperienceEventType.SITE_THEME_REPLACED.value in events


@pytest.mark.asyncio
async def test_theme_tenant_isolation(client):
    ws = await _workspace(client, code="iso-t")
    theme = await client.post(
        "/api/v1/themes",
        json={
            "workspace_id": ws["id"],
            "code": "iso",
            "name": "Iso",
            "direction_mode": "ltr",
        },
        headers=tenant_headers(TENANT_A),
    )
    assert theme.status_code == 201
    other = await client.get(
        f"/api/v1/themes/{theme.json()['id']}",
        headers=tenant_headers(TENANT_B),
    )
    assert other.status_code == 404


@pytest.mark.asyncio
async def test_theme_optimistic_lock(client):
    ws = await _workspace(client, code="lock-t")
    theme = await client.post(
        "/api/v1/themes",
        json={
            "workspace_id": ws["id"],
            "code": "lock",
            "name": "Lock",
        },
        headers=tenant_headers(TENANT_A),
    )
    body = theme.json()
    bad = await client.patch(
        f"/api/v1/themes/{body['id']}",
        json={"name": "Nope", "version": body["version"] + 9},
        headers=tenant_headers(TENANT_A),
    )
    assert bad.status_code == 409
