"""Component library API tests — Phase 11.2."""
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-comp"):
    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):
    site = await client.post(
        "/api/v1/sites",
        json={
            "workspace_id": workspace_id,
            "code": "main",
            "name": "Main Site",
        },
        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_112(client):
    resp = await client.get("/health")
    assert resp.status_code == 200
    assert resp.json()["version"].startswith("0.11.")

    caps = await client.get("/capabilities")
    body = caps.json()
    assert body["features"]["components"] is True
    assert body["features"]["component_versions"] is True
    assert body["features"]["component_version_immutability"] is True
    assert body["features"]["page_component_placements"] is True
    assert "hero" in body["component_kinds"]
    assert "menu_ref" in body["component_kinds"]


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

    component = await client.post(
        "/api/v1/components",
        json={
            "workspace_id": workspace_id,
            "code": "hero-main",
            "name": "Hero Main",
            "kind": "hero",
            "category": "marketing",
            "tags": ["hero", "landing"],
        },
        headers=tenant_headers(TENANT_A),
    )
    assert component.status_code == 201, component.text
    component_id = component.json()["id"]
    assert component.json()["status"] == "draft"

    version = await client.post(
        "/api/v1/component-versions",
        json={
            "workspace_id": workspace_id,
            "component_id": component_id,
            "definition_schema": {
                "type": "object",
                "properties": {"title": {"type": "string"}},
            },
            "props_defaults": {"title": "Welcome"},
            "render_hint": {"renderer": "web"},
            "changelog": "Initial draft",
        },
        headers=tenant_headers(TENANT_A),
    )
    assert version.status_code == 201, version.text
    version_id = version.json()["id"]
    assert version.json()["version_number"] == 1
    assert version.json()["status"] == "draft"
    assert version.json()["is_immutable"] is False

    listed = await client.get(
        f"/api/v1/components?workspace_id={workspace_id}&q=hero&sort_by=name&sort_dir=asc",
        headers=tenant_headers(TENANT_A),
    )
    assert listed.status_code == 200
    assert listed.json()["total"] >= 1
    assert any(i["id"] == component_id for i in listed.json()["items"])

    published = await client.post(
        f"/api/v1/component-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

    # Immutable: update must fail
    blocked = await client.patch(
        f"/api/v1/component-versions/{version_id}",
        json={"changelog": "nope", "version": published.json()["version"]},
        headers=tenant_headers(TENANT_A),
    )
    assert blocked.status_code == 409

    # Draft placement blocked before publish was already done; try unpublished second version
    v2 = await client.post(
        "/api/v1/component-versions",
        json={
            "workspace_id": workspace_id,
            "component_id": component_id,
            "changelog": "v2",
        },
        headers=tenant_headers(TENANT_A),
    )
    assert v2.status_code == 201
    draft_place = await client.post(
        "/api/v1/page-component-placements",
        json={
            "workspace_id": workspace_id,
            "site_id": site["id"],
            "page_id": page["id"],
            "component_id": component_id,
            "component_version_id": v2.json()["id"],
            "slot_key": "main.hero",
            "sort_order": 0,
        },
        headers=tenant_headers(TENANT_A),
    )
    assert draft_place.status_code == 409

    placement = await client.post(
        "/api/v1/page-component-placements",
        json={
            "workspace_id": workspace_id,
            "site_id": site["id"],
            "page_id": page["id"],
            "component_id": component_id,
            "component_version_id": version_id,
            "slot_key": "main.hero",
            "sort_order": 10,
            "props_override": {"title": "Hello"},
        },
        headers=tenant_headers(TENANT_A),
    )
    assert placement.status_code == 201, placement.text
    placement_id = placement.json()["id"]

    # Retire blocked while in use
    retire_blocked = await client.post(
        f"/api/v1/component-versions/{version_id}/retire",
        headers=tenant_headers(TENANT_A),
    )
    assert retire_blocked.status_code == 409

    # Soft-delete component blocked while placements exist
    delete_blocked = await client.post(
        f"/api/v1/components/{component_id}/delete",
        headers=tenant_headers(TENANT_A),
    )
    assert delete_blocked.status_code == 409

    removed = await client.post(
        f"/api/v1/page-component-placements/{placement_id}/delete",
        headers=tenant_headers(TENANT_A),
    )
    assert removed.status_code == 200
    assert removed.json()["is_deleted"] is True

    retired = await client.post(
        f"/api/v1/component-versions/{version_id}/retire",
        headers=tenant_headers(TENANT_A),
    )
    assert retired.status_code == 200
    assert retired.json()["status"] == "retired"

    archived = await client.post(
        f"/api/v1/components/{component_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.COMPONENT_CREATED.value in events
    assert ExperienceEventType.COMPONENT_VERSION_CREATED.value in events
    assert ExperienceEventType.COMPONENT_VERSION_PUBLISHED.value in events
    assert ExperienceEventType.PAGE_COMPONENT_PLACED.value in events
    assert ExperienceEventType.PAGE_COMPONENT_REMOVED.value in events
    assert ExperienceEventType.COMPONENT_VERSION_RETIRED.value in events
    assert ExperienceEventType.COMPONENT_ARCHIVED.value in events


@pytest.mark.asyncio
async def test_component_tenant_isolation(client):
    ws = await _workspace(client, code="iso-c")
    component = await client.post(
        "/api/v1/components",
        json={
            "workspace_id": ws["id"],
            "code": "iso",
            "name": "Iso",
            "kind": "content",
        },
        headers=tenant_headers(TENANT_A),
    )
    assert component.status_code == 201
    component_id = component.json()["id"]

    other = await client.get(
        f"/api/v1/components/{component_id}",
        headers=tenant_headers(TENANT_B),
    )
    assert other.status_code == 404


@pytest.mark.asyncio
async def test_optimistic_lock_on_component(client):
    ws = await _workspace(client, code="lock-c")
    component = await client.post(
        "/api/v1/components",
        json={
            "workspace_id": ws["id"],
            "code": "lock",
            "name": "Lock",
            "kind": "cta",
        },
        headers=tenant_headers(TENANT_A),
    )
    assert component.status_code == 201
    body = component.json()
    bad = await client.patch(
        f"/api/v1/components/{body['id']}",
        json={"name": "Nope", "version": body["version"] + 5},
        headers=tenant_headers(TENANT_A),
    )
    assert bad.status_code == 409
