"""API tests for the FlowPilot Phase 6 post-resolve templatization flow.

Covers:
- GET /api/v1/draft-templates list with pending_only filter.
- POST /{id}/accept → creates script_templates row with provenance fields,
  marks draft accepted + promoted_template_id set.
- POST /{id}/reject → marks rejected.
- 409 when accepting or rejecting a non-pending draft.
- Category validation (400 on unknown category_id).
- GET/PATCH /accounts/me/preferences round-trip.
"""
from __future__ import annotations

from datetime import datetime, timezone
from uuid import UUID

import pytest
from httpx import AsyncClient
from sqlalchemy import select

from app.models.account_settings import AccountSettings
from app.models.ai_session import AISession
from app.models.draft_template import DraftTemplate
from app.models.script_template import ScriptCategory, ScriptTemplate


async def _make_draft(
    test_db,
    user,
    *,
    proposed_name: str = "Test draft",
    status_: str = "pending",
    with_psa_ticket: bool = False,
) -> tuple[AISession, DraftTemplate]:
    session = AISession(
        user_id=user["user_data"]["id"],
        account_id=user["user_data"]["account_id"],
        session_type="chat",
        intake_type="free_text",
        intake_content={"text": "phase 6 test"},
        status="resolved",
        confidence_tier="discovery",
        conversation_messages=[],
        psa_ticket_id="48307" if with_psa_ticket else None,
    )
    test_db.add(session)
    await test_db.flush()

    draft = DraftTemplate(
        account_id=user["user_data"]["account_id"],
        source_session_id=session.id,
        source_user_id=user["user_data"]["id"],
        script_body='Do-Something -Target {{ target_name }}\n',
        proposed_parameters={
            "parameters": [
                {"key": "target_name", "label": "Target Name", "type": "text"},
            ],
        },
        proposed_name=proposed_name,
        status=status_,
    )
    test_db.add(draft)
    await test_db.commit()
    await test_db.refresh(draft)
    return session, draft


async def _make_category(test_db) -> ScriptCategory:
    cat = ScriptCategory(
        name="Phase 6 Test Category",
        slug=f"phase-6-test-{datetime.now(timezone.utc).timestamp()}",
        description="test",
        is_active=True,
    )
    test_db.add(cat)
    await test_db.commit()
    await test_db.refresh(cat)
    return cat


# ── List ─────────────────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_list_pending_only_default(
    client: AsyncClient, test_user, auth_headers, test_db
):
    await _make_draft(test_db, test_user, proposed_name="pending-a", status_="pending")
    await _make_draft(test_db, test_user, proposed_name="accepted-b", status_="accepted")

    r = await client.get("/api/v1/draft-templates", headers=auth_headers)
    assert r.status_code == 200
    drafts = r.json()["drafts"]
    names = {d["proposed_name"] for d in drafts}
    assert "pending-a" in names
    assert "accepted-b" not in names


@pytest.mark.asyncio
async def test_list_with_pending_only_false_includes_all(
    client: AsyncClient, test_user, auth_headers, test_db
):
    await _make_draft(test_db, test_user, proposed_name="pending-c", status_="pending")
    await _make_draft(test_db, test_user, proposed_name="rejected-d", status_="rejected")

    r = await client.get(
        "/api/v1/draft-templates?pending_only=false", headers=auth_headers,
    )
    assert r.status_code == 200
    names = {d["proposed_name"] for d in r.json()["drafts"]}
    assert {"pending-c", "rejected-d"}.issubset(names)


# ── Accept ───────────────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_accept_creates_template_with_provenance(
    client: AsyncClient, test_user, auth_headers, test_db
):
    session, draft = await _make_draft(test_db, test_user, with_psa_ticket=True)
    cat = await _make_category(test_db)

    r = await client.post(
        f"/api/v1/draft-templates/{draft.id}/accept",
        headers=auth_headers,
        json={
            "name": "Do Something On Target",
            "category_id": str(cat.id),
            "description": "promoted from phase 6 test",
            "parameters_schema": {
                "parameters": [
                    {"key": "target_name", "label": "Target", "field_type": "text"},
                ],
            },
        },
    )
    assert r.status_code == 201
    body = r.json()
    assert body["draft_id"] == str(draft.id)
    assert body["promoted_template_id"] is not None
    assert body["template_slug"] == "do-something-on-target"

    # Draft row is now accepted with the promoted template ID set.
    await test_db.refresh(draft)
    assert draft.status == "accepted"
    assert draft.promoted_template_id == UUID(body["promoted_template_id"])
    assert draft.resolved_at is not None

    # New template row exists with provenance fields populated.
    tpl_result = await test_db.execute(
        select(ScriptTemplate).where(ScriptTemplate.id == UUID(body["promoted_template_id"]))
    )
    tpl = tpl_result.scalar_one()
    assert tpl.source_session_id == session.id
    assert tpl.source_user_id == UUID(test_user["user_data"]["id"])
    assert tpl.source_ticket_ref == "CW #48307"
    assert tpl.script_body == draft.script_body  # edited_body was not supplied


@pytest.mark.asyncio
async def test_accept_with_edited_body_overrides_draft(
    client: AsyncClient, test_user, auth_headers, test_db
):
    _, draft = await _make_draft(test_db, test_user)
    cat = await _make_category(test_db)

    r = await client.post(
        f"/api/v1/draft-templates/{draft.id}/accept",
        headers=auth_headers,
        json={
            "name": "Edited Body Test",
            "category_id": str(cat.id),
            "parameters_schema": {"parameters": []},
            "edited_body": 'Write-Host "edited final version"\n',
        },
    )
    assert r.status_code == 201
    tpl = (
        await test_db.execute(
            select(ScriptTemplate).where(
                ScriptTemplate.id == UUID(r.json()["promoted_template_id"])
            )
        )
    ).scalar_one()
    assert tpl.script_body == 'Write-Host "edited final version"\n'


@pytest.mark.asyncio
async def test_accept_rejects_unknown_category(
    client: AsyncClient, test_user, auth_headers, test_db
):
    _, draft = await _make_draft(test_db, test_user)
    bogus_cat = "00000000-0000-0000-0000-000000000000"
    r = await client.post(
        f"/api/v1/draft-templates/{draft.id}/accept",
        headers=auth_headers,
        json={
            "name": "x",
            "category_id": bogus_cat,
            "parameters_schema": {"parameters": []},
        },
    )
    assert r.status_code == 400


@pytest.mark.asyncio
async def test_accept_already_accepted_returns_409(
    client: AsyncClient, test_user, auth_headers, test_db
):
    _, draft = await _make_draft(test_db, test_user, status_="accepted")
    cat = await _make_category(test_db)
    r = await client.post(
        f"/api/v1/draft-templates/{draft.id}/accept",
        headers=auth_headers,
        json={
            "name": "x",
            "category_id": str(cat.id),
            "parameters_schema": {"parameters": []},
        },
    )
    assert r.status_code == 409


# ── Reject ───────────────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_reject_marks_draft_rejected(
    client: AsyncClient, test_user, auth_headers, test_db
):
    _, draft = await _make_draft(test_db, test_user)
    r = await client.post(
        f"/api/v1/draft-templates/{draft.id}/reject", headers=auth_headers,
    )
    assert r.status_code == 200
    assert r.json()["status"] == "rejected"
    await test_db.refresh(draft)
    assert draft.status == "rejected"
    assert draft.resolved_at is not None


@pytest.mark.asyncio
async def test_reject_already_accepted_returns_409(
    client: AsyncClient, test_user, auth_headers, test_db
):
    _, draft = await _make_draft(test_db, test_user, status_="accepted")
    r = await client.post(
        f"/api/v1/draft-templates/{draft.id}/reject", headers=auth_headers,
    )
    assert r.status_code == 409


# ── Preferences ──────────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_get_preferences_empty_by_default(
    client: AsyncClient, auth_headers,
):
    r = await client.get("/api/v1/accounts/me/preferences", headers=auth_headers)
    assert r.status_code == 200
    assert r.json()["preferences"] == {}


@pytest.mark.asyncio
async def test_patch_preferences_merges_keys(
    client: AsyncClient, test_user, auth_headers, test_db
):
    # First write: one key.
    r = await client.patch(
        "/api/v1/accounts/me/preferences",
        headers=auth_headers,
        json={"preferences": {"templatize_prompt_enabled": False}},
    )
    assert r.status_code == 200
    assert r.json()["preferences"]["templatize_prompt_enabled"] is False

    # Second write: different key — first must be preserved (merge semantics).
    r2 = await client.patch(
        "/api/v1/accounts/me/preferences",
        headers=auth_headers,
        json={"preferences": {"cw_resolved_status_id": 42}},
    )
    assert r2.status_code == 200
    prefs = r2.json()["preferences"]
    assert prefs["templatize_prompt_enabled"] is False
    assert prefs["cw_resolved_status_id"] == 42

    # Stored on the account_settings row.
    stored = (
        await test_db.execute(
            select(AccountSettings.preferences).where(
                AccountSettings.account_id == UUID(test_user["user_data"]["account_id"])
            )
        )
    ).scalar_one()
    assert stored["templatize_prompt_enabled"] is False
    assert stored["cw_resolved_status_id"] == 42
