"""Promoting an App between environments (tenants): export from one, import
into another — and importing it again updates what the first import created
instead of duplicating it (app/crud/app_bundle.py, app_import_links)."""

import io
import json
import uuid
from datetime import datetime, timezone

import pytest_asyncio
from sqlalchemy import delete, select, text

from app.core.auth import create_access_token
from tests.conftest import AsyncSessionLocal
from app.models.identity import Role, Tenant, User, UserRole
from app.models.master import Master
from app.models.navigation import Page


@pytest_asyncio.fixture
async def dev_env():
    """A second tenant playing "dev", with its own administrator."""
    unique = uuid.uuid4().hex[:8]
    async with AsyncSessionLocal() as db:
        async with db.begin():
            tenant = Tenant(name=f"Pytest Dev {unique}", status="active", license_set_at=datetime.now(timezone.utc))
            db.add(tenant)
            await db.flush()
            role = Role(tenant_id=tenant.id, name="Dev Admin", is_system_role=True, action_matrix={})
            user = User(tenant_id=tenant.id, name="Dev Admin", email=f"dev-{unique}@example.com", status="active")
            db.add_all([role, user])
            await db.flush()
            db.add(UserRole(user_id=user.id, role_id=role.id))
            tid, uid = tenant.id, user.id
    yield {"tenant_id": tid, "headers": {"Authorization": f"Bearer {create_access_token(tenant_id=tid, user_id=uid, vendor_id=None)}"}}
    async with AsyncSessionLocal() as db:
        async with db.begin():
            for table in ("audit_log_entries", "page_versions", "pages", "forms", "master_records", "masters", "apps"):
                await db.execute(text(f"DELETE FROM {table} WHERE tenant_id = :tid"), {"tid": str(tid)})
            await db.execute(delete(UserRole).where(UserRole.user_id == uid))
            await db.execute(delete(User).where(User.tenant_id == tid))
            await db.execute(delete(Role).where(Role.tenant_id == tid))
            await db.execute(delete(Tenant).where(Tenant.id == tid))


async def _export(client, headers, app_id) -> bytes:
    resp = await client.get(f"/apps/{app_id}/export", headers=headers)
    assert resp.status_code == 200, resp.text
    return resp.content


async def _import(client, headers, bundle: bytes) -> dict:
    resp = await client.post("/apps/import", headers=headers, files={"file": ("app.json", io.BytesIO(bundle), "application/json")})
    assert resp.status_code == 201, resp.text
    return resp.json()


async def test_reimport_updates_instead_of_duplicating(client, auth_headers, test_tenant, dev_env):
    dev = dev_env["headers"]
    master = (await client.post("/masters", headers=dev, json={"name": "Offices", "field_schema": [{"name": "city", "type": "text"}]})).json()
    form = (await client.post("/forms", headers=dev, json={"name": "Visit", "field_definitions": [
        {"name": "office", "type": "dropdown", "label": "Office", "master_id": master["id"], "master_display_field": "city"},
    ]})).json()
    await client.post(f"/forms/{form['form_id']}/publish", headers=dev)
    page = (await client.post("/pages", headers=dev, json={"name": "Home", "parameters": [{"name": "officeId"}], "canvas_width": 900,
                                                             "blocks": [{"id": "f", "type": "form", "data_source": f"form:{form['form_id']}"}]})).json()
    await client.post(f"/pages/{page['id']}/publish", headers=dev)
    # An unpublished edit in dev isn't what gets promoted.
    await client.patch(f"/pages/{page['id']}", headers=dev, json={"name": "Home (wip)"})
    app = (await client.post("/apps", headers=dev, json={"name": "Visits", "items": [
        {"type": "master", "id": master["id"]}, {"type": "form", "id": form["form_id"]}, {"type": "page", "id": page["id"]},
    ]})).json()

    first = await _import(client, auth_headers, await _export(client, dev, app["id"]))
    assert first["created"]["master"] == 1 and first["created"]["form"] == 1 and first["created"]["page"] == 1
    assert sum(first["updated"].values()) == 0

    async with AsyncSessionLocal() as db:
        pages = (await db.execute(select(Page).where(Page.tenant_id == test_tenant["tenant_id"]))).scalars().all()
        masters = (await db.execute(select(Master).where(Master.tenant_id == test_tenant["tenant_id"]))).scalars().all()
    assert [(p.name, p.canvas_width, p.parameters) for p in pages] == [("Home", 900, [{"name": "officeId"}])]
    target_master_id = str(masters[0].id)
    imported_forms = [f for f in (await client.get("/forms", headers=auth_headers)).json() if f["name"] == "Visit"]
    target_form_id = imported_forms[0]["form_id"]
    assert imported_forms[0]["field_definitions"][0]["master_id"] == target_master_id
    assert pages[0].blocks[0]["data_source"] == f"form:{target_form_id}"

    # Publish in "prod", then change things in dev and promote again.
    await client.post(f"/forms/{target_form_id}/publish", headers=auth_headers)
    await client.post(f"/pages/{pages[0].id}/publish", headers=auth_headers)
    await client.post(f"/forms/{form['form_id']}/new-version", headers=dev)
    await client.patch(f"/forms/{form['form_id']}", headers=dev, json={"field_definitions": [
        {"name": "office", "type": "dropdown", "label": "Office", "master_id": master["id"], "master_display_field": "city"},
        {"name": "notes", "type": "text", "label": "Notes"},
    ]})
    await client.post(f"/forms/{form['form_id']}/publish", headers=dev)
    await client.patch(f"/masters/{master['id']}", headers=dev, json={"field_schema": [{"name": "city", "type": "text"}, {"name": "pin", "type": "text"}]})

    second = await _import(client, auth_headers, await _export(client, dev, app["id"]))
    assert sum(second["created"].values()) == 0
    assert second["updated"]["master"] == 1 and second["updated"]["form"] == 1 and second["updated"]["page"] == 1

    async with AsyncSessionLocal() as db:
        assert len((await db.execute(select(Master).where(Master.tenant_id == test_tenant["tenant_id"]))).scalars().all()) == 1
        assert (await db.get(Master, uuid.UUID(target_master_id))).field_schema[1]["name"] == "pin"
    # The form update arrives as a draft; prod keeps serving v1 until it's published there.
    current = (await client.get(f"/forms/{target_form_id}", headers=auth_headers)).json()
    live = (await client.get(f"/forms/{target_form_id}/live", headers=auth_headers)).json()
    assert current["version"] == 2 and current["status"] == "draft" and [f["name"] for f in current["field_definitions"]] == ["office", "notes"]
    assert live["version"] == 1
    page_now = (await client.get(f"/pages/{pages[0].id}", headers=auth_headers)).json()
    assert page_now["published_version"] == 1


async def test_bundle_carries_page_events_and_dashboard_widget_config(client, auth_headers, dev_env):
    dev = dev_env["headers"]
    page = (await client.post("/pages", headers=dev, json={"name": "With events", "events": [{"trigger": "on_load", "action_type": "show_message", "message": "Hi"}]})).json()
    app = (await client.post("/apps", headers=dev, json={"name": "E", "items": [{"type": "page", "id": page["id"]}]})).json()
    bundle = json.loads(await _export(client, dev, app["id"]))
    assert bundle["items"]["page"][0]["events"][0]["message"] == "Hi"
    result = await _import(client, auth_headers, json.dumps(bundle).encode())
    assert result["created"]["page"] == 1


async def test_html_pages_promote_with_their_document_and_links(client, auth_headers, test_tenant, dev_env):
    dev = dev_env["headers"]
    about = (await client.post("/pages", headers=dev, json={"name": "About", "content_mode": "html"})).json()
    home = (await client.post("/pages", headers=dev, json={"name": "Home", "content_mode": "html", "seo": {"title": "Welcome home", "description": "Hi"}})).json()
    await client.patch(f"/pages/{about['id']}", headers=dev, json={"html_document": "<h1>About</h1>"})
    await client.patch(f"/pages/{home['id']}", headers=dev, json={"html_document": f"<h1>Home</h1><a href='ff-page:{about['id']}'>About</a>"})
    for pid in (about["id"], home["id"]):
        await client.post(f"/pages/{pid}/publish", headers=dev)
    app = (await client.post("/apps", headers=dev, json={"name": "Site", "items": [{"type": "page", "id": home["id"]}, {"type": "page", "id": about["id"]}]})).json()

    await _import(client, auth_headers, await _export(client, dev, app["id"]))
    async with AsyncSessionLocal() as db:
        pages = {p.name: p for p in (await db.execute(select(Page).where(Page.tenant_id == test_tenant["tenant_id"]))).scalars().all()}
    assert pages["Home"].content_mode == "html"
    assert pages["Home"].seo["title"] == "Welcome home"
    # The link points at the About page created here, not the one in dev.
    assert f"ff-page:{pages['About'].id}" in pages["Home"].html_document
    assert about["id"] not in pages["Home"].html_document
    # Being on the public website is each environment's own decision.
    assert pages["Home"].is_public is False
