"""License renewal requests (app/crud/license_requests.py) and the rule
that a tenant can't be used without a license (app/crud/licensing.py)."""

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 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():
            for table, column in (("license_files", "issued_by"), ("license_requests", "resolved_by")):
                await db.execute(text(f"UPDATE {table} SET {column} = NULL WHERE {column} = :id"), {"id": admin_id})
            await db.execute(text("DELETE FROM platform_admins WHERE id = :id"), {"id": admin_id})


def _login(tenant) -> dict:
    return {"tenant_id": str(tenant["tenant_id"]), "email": tenant["admin_email"], "password": TEST_ADMIN_PASSWORD}


async def _unlicense(tenant_id) -> None:
    async with AsyncSessionLocal() as db:
        async with db.begin():
            (await db.get(Tenant, tenant_id)).license_set_at = None
    licensing.forget(tenant_id)


# ---- a license is required ----------------------------------------------------------------------------------


async def test_an_unlicensed_tenant_cant_be_used_until_it_gets_one(client, test_tenant, auth_headers, super_admin):
    tid = test_tenant["tenant_id"]
    await _unlicense(tid)
    resp = await client.post("/auth/login", json=_login(test_tenant))
    assert resp.status_code == 403
    assert resp.json()["detail"]["code"] == "license_inactive"
    assert "doesn't have a license yet" in resp.json()["detail"]["message"]
    assert (await client.get("/users", headers=auth_headers)).status_code == 403
    assert (await client.get(f"/admin/tenants/{tid}/license", headers=super_admin)).json()["state"] == "unlicensed"

    # A license file from the sign-in page licenses it...
    issued = (await client.post(f"/admin/tenants/{tid}/license-files", headers=super_admin, json={})).json()
    assert (await client.post("/auth/license-file/apply", json={"content": issued["content"]})).status_code == 200
    assert (await client.post("/auth/login", json=_login(test_tenant))).status_code == 200
    status_ = (await client.get(f"/admin/tenants/{tid}/license", headers=super_admin)).json()
    assert status_["state"] == "unlimited"  # licensed, with no limits

    # ...and the Super Admin can take it away again, or license it directly.
    assert (await client.delete(f"/admin/tenants/{tid}/license", headers=super_admin)).json()["state"] == "unlicensed"
    assert (await client.post("/auth/login", json=_login(test_tenant))).status_code == 403
    assert (await client.put(f"/admin/tenants/{tid}/license", headers=super_admin, json={})).json()["state"] == "unlimited"
    assert (await client.post("/auth/login", json=_login(test_tenant))).status_code == 200


async def test_a_new_tenant_is_unlicensed_unless_given_one(client, super_admin):
    created = []
    try:
        for body in ({"name": f"Pytest Tenant {uuid.uuid4().hex[:6]}"}, {"name": f"Pytest Tenant {uuid.uuid4().hex[:6]}", "license": {}}):
            resp = await client.post("/admin/tenants", headers=super_admin, json=body)
            created.append(resp.json()["id"])
        tenants = {t["id"]: t for t in (await client.get("/admin/tenants", headers=super_admin)).json()}
        assert tenants[created[0]]["licensed"] is False
        assert tenants[created[1]]["licensed"] is True
    finally:
        async with AsyncSessionLocal() as db:
            async with db.begin():
                for tid in created:
                    await db.execute(text("DELETE FROM roles WHERE tenant_id = :t"), {"t": tid})
                    await db.execute(text("DELETE FROM tenants WHERE id = :t"), {"t": tid})


# ---- renewal requests -------------------------------------------------------------------------------------


async def test_the_tenant_asks_and_applies_the_renewal_it_was_issued(client, test_tenant, auth_headers, super_admin):
    tid = test_tenant["tenant_id"]
    await client.put(f"/admin/tenants/{tid}/license", headers=super_admin, json={"max_users": 5, "ends_on": str(TODAY + timedelta(days=20))})
    wanted = str(TODAY + timedelta(days=385))

    assert (await client.post("/tenants/me/license-requests", headers=auth_headers, json={})).status_code == 422  # says nothing
    resp = await client.post("/tenants/me/license-requests", headers=auth_headers, json={"max_users": 25, "ends_on": wanted, "message": "Renewing for next year"})
    assert resp.status_code == 201, resp.text
    request = resp.json()
    assert request["status"] == "open" and request["requested_by_name"] == "Pytest Admin"
    # One open at a time.
    assert (await client.post("/tenants/me/license-requests", headers=auth_headers, json={"message": "again"})).status_code == 409

    # The Super Admin sees it among every tenant's open requests...
    open_requests = (await client.get("/admin/license-requests", headers=super_admin)).json()
    mine = next(r for r in open_requests if r["id"] == request["id"])
    assert mine["tenant_name"].startswith("Pytest Tenant") and mine["max_users"] == 25

    # ...and answers it with a license file.
    resp = await client.post(
        f"/admin/tenants/{tid}/license-files?request_id={request['id']}", headers=super_admin, json={"max_users": 25, "ends_on": wanted}
    )
    assert resp.status_code == 201, resp.text
    assert request["id"] not in {r["id"] for r in (await client.get("/admin/license-requests", headers=super_admin)).json()}

    [answered] = (await client.get("/tenants/me/license-requests", headers=auth_headers)).json()
    assert answered["status"] == "fulfilled" and answered["license"]["max_users"] == 25 and not answered["file_applied"]
    notes = (await client.get("/notifications", headers=auth_headers)).json()
    assert any(n["title"] == "Your license renewal is ready" for n in notes)

    # Applied from the Studio — no file to pass around.
    applied = await client.post(f"/tenants/me/license-requests/{request['id']}/apply", headers=auth_headers)
    assert applied.status_code == 200, applied.text
    assert applied.json()["license"] == {"max_users": 25, "max_portal_users": None, "starts_on": None, "ends_on": wanted}
    assert (await client.get("/tenants/me/license-requests", headers=auth_headers)).json()[0]["file_applied"] is True
    assert (await client.post(f"/tenants/me/license-requests/{request['id']}/apply", headers=auth_headers)).status_code == 409


async def test_a_request_can_be_declined_or_cancelled(client, test_tenant, auth_headers, super_admin):
    tid = test_tenant["tenant_id"]
    first = (await client.post("/tenants/me/license-requests", headers=auth_headers, json={"max_users": 500})).json()
    resp = await client.post(f"/admin/tenants/{tid}/license-requests/{first['id']}/decline", headers=super_admin, json={"response": "500 is more than your plan allows."})
    assert resp.status_code == 204
    notes = (await client.get("/notifications", headers=auth_headers)).json()
    assert any(n["title"] == "Your license request was declined" and n["message"] == "500 is more than your plan allows." for n in notes)
    # Answered — can't be answered again, or cancelled.
    assert (await client.post(f"/admin/tenants/{tid}/license-requests/{first['id']}/decline", headers=super_admin, json={"response": "x"})).status_code == 409
    assert (await client.post(f"/tenants/me/license-requests/{first['id']}/cancel", headers=auth_headers)).status_code == 409

    second = (await client.post("/tenants/me/license-requests", headers=auth_headers, json={"message": "More Portal seats please"})).json()
    assert (await client.post(f"/tenants/me/license-requests/{second['id']}/cancel", headers=auth_headers)).status_code == 204
    statuses = [r["status"] for r in (await client.get(f"/admin/tenants/{tid}/license-requests", headers=super_admin)).json()]
    assert statuses == ["cancelled", "declined"]
    # Nothing to apply on a request that wasn't fulfilled.
    assert (await client.post(f"/tenants/me/license-requests/{second['id']}/apply", headers=auth_headers)).status_code == 409
