"""License files (app/crud/license_files.py): the Super Admin issues one,
the tenant's administrator applies it — from the Studio, or from the
sign-in page once the license has ended."""

import uuid
from datetime import timedelta

import pytest
from sqlalchemy import text

from app.core.auth import create_platform_admin_token, hash_password
from app.crud import license_files, licensing
from app.models.identity import Tenant
from app.models.platform_admin import PlatformAdmin
from tests.conftest import TEST_ADMIN_PASSWORD, AsyncSessionLocal

TODAY = licensing.today()


@pytest.fixture
async def super_admin():
    async with AsyncSessionLocal() as db:
        async with db.begin():
            admin = PlatformAdmin(
                name="Pytest Super Admin", email=f"pytest-super-{uuid.uuid4().hex[:8]}@example.com",
                password_hash=hash_password("pytest-password-123"), status="active",
            )
            db.add(admin)
            await db.flush()
            admin_id = admin.id
    yield {"Authorization": f"Bearer {create_platform_admin_token(admin_id=admin_id)}"}
    async with AsyncSessionLocal() as db:
        async with db.begin():
            await db.execute(text("UPDATE license_files SET issued_by = NULL WHERE issued_by = :id"), {"id": admin_id})
            await db.execute(text("DELETE FROM platform_admins WHERE id = :id"), {"id": admin_id})


async def _issue(client, super_admin, tenant_id, **terms) -> dict:
    resp = await client.post(f"/admin/tenants/{tenant_id}/license-files", headers=super_admin, json=terms)
    assert resp.status_code == 201, resp.text
    return resp.json()


async def _set_license(tenant_id, **fields) -> None:
    async with AsyncSessionLocal() as db:
        async with db.begin():
            tenant = await db.get(Tenant, tenant_id)
            for key, value in fields.items():
                setattr(tenant, f"license_{key}", value)
    licensing.forget(tenant_id)


def test_a_file_can_be_read_back_but_not_changed():
    content = license_files.seal({"v": 1, "id": "x", "tenant_id": str(uuid.uuid4()), "max_users": 5})
    assert content.startswith("-----BEGIN FLOWFABRIC LICENSE-----\n")
    assert "max_users" not in content  # encrypted, not just encoded
    assert license_files.unseal(content)["max_users"] == 5
    # Re-wrapped by an email client: still reads.
    assert license_files.unseal(content.replace("\n", "\r\n  "))["max_users"] == 5
    lines = content.splitlines()
    body = lines[1]
    tampered = "\n".join([lines[0], body[:20] + ("A" if body[20] != "A" else "B") + body[21:], *lines[2:]])
    with pytest.raises(license_files.LicenseFileError, match="isn't a valid license file"):
        license_files.unseal(tampered)
    with pytest.raises(license_files.LicenseFileError):
        license_files.unseal("hello")


async def test_the_tenant_applies_a_file_the_super_admin_issued(client, test_tenant, super_admin, auth_headers):
    tid = test_tenant["tenant_id"]
    ends = TODAY + timedelta(days=365)
    issued = await _issue(client, super_admin, tid, max_users=25, max_portal_users=100, starts_on=str(TODAY), ends_on=str(ends))
    assert issued["filename"].endswith(f"-license-until-{ends}.lic")
    assert issued["file"]["status"] == "waiting"
    # Issuing changes nothing yet.
    assert (await client.get("/tenants/me/license", headers=auth_headers)).json()["license"]["max_users"] is None

    content = issued["content"]
    check = await client.post("/tenants/me/license-file/check", headers=auth_headers, json={"content": content})
    assert check.status_code == 200, check.text
    assert check.json()["license"] == {"max_users": 25, "max_portal_users": 100, "starts_on": str(TODAY), "ends_on": str(ends)}
    assert check.json()["current"]["max_users"] is None

    applied = await client.post("/tenants/me/license-file/apply", headers=auth_headers, json={"content": content})
    assert applied.status_code == 200, applied.text
    assert applied.json()["license"]["max_users"] == 25 and applied.json()["state"] == "active"

    # Once only.
    again = await client.post("/tenants/me/license-file/apply", headers=auth_headers, json={"content": content})
    assert again.status_code == 409 and "already applied" in again.json()["detail"]

    files = (await client.get(f"/admin/tenants/{tid}/license-files", headers=super_admin)).json()
    assert files[0]["status"] == "applied" and files[0]["applied_by_name"] == "Pytest Admin"
    redownload = (await client.get(f"/admin/tenants/{tid}/license-files/{files[0]['id']}/download", headers=super_admin)).json()
    assert license_files.unseal(redownload["content"])["id"] == files[0]["id"]

    async with AsyncSessionLocal() as db:
        actions = set((await db.execute(text("SELECT action FROM audit_log_entries WHERE tenant_id = :t"), {"t": tid})).scalars())
    assert {"tenant.license_file_issued", "tenant.license_file_applied"} <= actions


async def test_a_file_only_fits_its_own_tenant_and_order(client, test_tenant, super_admin, auth_headers):
    tid = test_tenant["tenant_id"]
    other = (await client.post("/admin/tenants", headers=super_admin, json={"name": f"Pytest Tenant {uuid.uuid4().hex[:6]}"})).json()["id"]
    try:
        foreign = await _issue(client, super_admin, other, max_users=3)
        resp = await client.post("/tenants/me/license-file/check", headers=auth_headers, json={"content": foreign["content"]})
        assert resp.status_code == 422 and "different workspace" in resp.json()["detail"]
    finally:
        async with AsyncSessionLocal() as db:
            async with db.begin():
                await db.execute(text("SELECT set_config('app.current_tenant_id', :v, true)"), {"v": other})
                for table in ("license_files", "audit_log_entries", "roles"):
                    await db.execute(text(f"DELETE FROM {table} WHERE tenant_id = :t"), {"t": other})
                await db.execute(text("DELETE FROM tenants WHERE id = :t"), {"t": other})

    older = await _issue(client, super_admin, tid, max_users=10)
    newer = await _issue(client, super_admin, tid, max_users=20)
    assert (await client.post("/tenants/me/license-file/apply", headers=auth_headers, json={"content": newer["content"]})).status_code == 200
    resp = await client.post("/tenants/me/license-file/apply", headers=auth_headers, json={"content": older["content"]})
    assert resp.status_code == 409 and "newer license file" in resp.json()["detail"]
    statuses = {f["id"]: f["status"] for f in (await client.get(f"/admin/tenants/{tid}/license-files", headers=super_admin)).json()}
    assert statuses[older["file"]["id"]] == "superseded"

    # A withdrawn file can't be applied; an applied one can't be withdrawn.
    withdrawn = await _issue(client, super_admin, tid, max_users=30)
    assert (await client.post(f"/admin/tenants/{tid}/license-files/{withdrawn['file']['id']}/revoke", headers=super_admin)).status_code == 204
    resp = await client.post("/tenants/me/license-file/apply", headers=auth_headers, json={"content": withdrawn["content"]})
    assert resp.status_code == 422 and "withdrawn" in resp.json()["detail"]
    assert (await client.post(f"/admin/tenants/{tid}/license-files/{newer['file']['id']}/revoke", headers=super_admin)).status_code == 409

    # A file for a license already over is refused up front.
    resp = await client.post(f"/admin/tenants/{tid}/license-files", headers=super_admin, json={"ends_on": str(TODAY - timedelta(days=1))})
    assert resp.status_code == 422


async def test_an_ended_license_is_renewed_from_the_sign_in_page(client, test_tenant, super_admin):
    tid = test_tenant["tenant_id"]
    login = {"tenant_id": str(tid), "email": test_tenant["admin_email"], "password": TEST_ADMIN_PASSWORD}
    renewal = await _issue(client, super_admin, tid, max_users=5, ends_on=str(TODAY + timedelta(days=30)))

    # While the license is in effect, it's applied from inside the Studio.
    resp = await client.post("/auth/license-file/check", json={"content": renewal["content"]})
    assert resp.status_code == 409

    await _set_license(tid, starts_on=TODAY - timedelta(days=60), ends_on=TODAY - timedelta(days=1))
    assert (await client.post("/auth/login", json=login)).status_code == 403
    check = await client.post("/auth/license-file/check", json={"content": renewal["content"]})
    assert check.status_code == 200, check.text
    assert check.json()["current"]["ends_on"] == str(TODAY - timedelta(days=1))
    applied = await client.post("/auth/license-file/apply", json={"content": renewal["content"]})
    assert applied.status_code == 200 and "You can sign in now" in applied.json()["detail"]
    assert (await client.post("/auth/login", json=login)).status_code == 200

    assert (await client.post("/auth/license-file/check", json={"content": "not a license"})).status_code == 422
