"""
License renewal requests: a tenant's administrator asks the Super Admin for
a renewal or more seats, and the Super Admin answers.

- **Asking.** From the Studio, with the seats and end date wanted (each
  optional) and a message. One open request per tenant at a time; the
  administrator can cancel it.
- **Answering.** The Super Admin issues a license file for the request
  (app/crud/license_files.py), usually with the terms asked for, or
  declines it with a reason. Either way the requester gets an in-app
  notification.
- **Applying.** The tenant applies the file issued for its request from
  the Studio, with no file to pass around. The file is still there to
  download for sending some other way.

A tenant whose license has already ended can't sign in, so it can't ask
here — which is why the Studio warns its administrators 30 days ahead. The
Super Admin can still issue it a file, applied from the sign-in page.
"""

import uuid
from datetime import datetime, timezone

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.crud import license_files
from app.crud.audit import write_audit_log
from app.crud.license_files import LicenseFileError
from app.models.identity import LicenseFile, LicenseRequest, Tenant, User
from app.models.notification import Notification
from app.schemas.identity import LicenseRequestCreate, LicenseRequestRead, TenantLicense


async def _open(db: AsyncSession, tenant_id: uuid.UUID) -> LicenseRequest | None:
    return (
        await db.execute(select(LicenseRequest).where(LicenseRequest.tenant_id == tenant_id, LicenseRequest.status == "open"))
    ).scalar_one_or_none()


async def _get(db: AsyncSession, tenant_id: uuid.UUID, request_id: uuid.UUID) -> LicenseRequest:
    request = (
        await db.execute(select(LicenseRequest).where(LicenseRequest.tenant_id == tenant_id, LicenseRequest.id == request_id).with_for_update())
    ).scalar_one_or_none()
    if request is None:
        raise LicenseFileError("License request not found.", 404)
    return request


async def history(db: AsyncSession, tenant_id: uuid.UUID, *, tenant_name: str | None = None, open_only: bool = False) -> list[LicenseRequestRead]:
    """The tenant's requests, newest first. `db` must be scoped to the tenant."""
    stmt = (
        select(LicenseRequest, User.name, LicenseFile)
        .outerjoin(User, User.id == LicenseRequest.requested_by)
        .outerjoin(LicenseFile, LicenseFile.id == LicenseRequest.license_file_id)
        .where(LicenseRequest.tenant_id == tenant_id)
        .order_by(LicenseRequest.created_at.desc())
    )
    if open_only:
        stmt = stmt.where(LicenseRequest.status == "open")
    result = []
    for request, requested_by_name, file in (await db.execute(stmt)).all():
        read = LicenseRequestRead.model_validate(request)
        read.tenant_name = tenant_name
        read.requested_by_name = requested_by_name
        if file is not None:
            read.license = TenantLicense.model_validate(file.license)
            read.file_applied = file.applied_at is not None
        result.append(read)
    return result


async def _notify(db: AsyncSession, request: LicenseRequest, title: str, message: str) -> None:
    if request.requested_by:
        db.add(Notification(tenant_id=request.tenant_id, title=title, message=message[:1000], source="license", recipient_user_id=request.requested_by))


# ---- the tenant ---------------------------------------------------------------------------------------------


async def create(db: AsyncSession, tenant_id: uuid.UUID, user_id: uuid.UUID | None, payload: LicenseRequestCreate) -> LicenseRequest:
    if await _open(db, tenant_id):
        raise LicenseFileError("There's already a request waiting for an answer. Cancel it to ask for something else.", 409)
    request = LicenseRequest(
        tenant_id=tenant_id, requested_by=user_id, max_users=payload.max_users, max_portal_users=payload.max_portal_users,
        ends_on=payload.ends_on, message=payload.message, status="open", created_at=datetime.now(timezone.utc),
    )
    db.add(request)
    await db.flush()
    await write_audit_log(db, tenant_id, user_id, "tenant.license_requested", "license_request", request.id, payload.model_dump(mode="json"))
    return request


async def cancel(db: AsyncSession, tenant_id: uuid.UUID, request_id: uuid.UUID, user_id: uuid.UUID | None) -> None:
    request = await _get(db, tenant_id, request_id)
    if request.status != "open":
        raise LicenseFileError("This request has already been answered.", 409)
    request.status = "cancelled"
    request.resolved_at = datetime.now(timezone.utc)
    await write_audit_log(db, tenant_id, user_id, "tenant.license_request_cancelled", "license_request", request.id)


async def apply(db: AsyncSession, tenant: Tenant, request_id: uuid.UUID, user_id: uuid.UUID | None) -> None:
    """Applies the license file issued for a request."""
    request = await _get(db, tenant.id, request_id)
    file = await db.get(LicenseFile, request.license_file_id) if request.license_file_id else None
    if request.status != "fulfilled" or file is None:
        raise LicenseFileError("There's no license issued for this request yet.", 409)
    await license_files.apply(db, tenant, license_files.content_for(tenant, file), user_id)


# ---- the Super Admin --------------------------------------------------------------------------------------


async def fulfil(db: AsyncSession, tenant_id: uuid.UUID, request_id: uuid.UUID, file: LicenseFile, admin_id: uuid.UUID) -> None:
    """Answers an open request with a license file just issued for it."""
    request = await _get(db, tenant_id, request_id)
    if request.status != "open":
        raise LicenseFileError("This request has already been answered or cancelled.", 409)
    request.status = "fulfilled"
    request.resolved_at = datetime.now(timezone.utc)
    request.resolved_by = admin_id
    request.license_file_id = file.id
    await _notify(
        db, request, "Your license renewal is ready",
        "A new license has been issued for this workspace. Apply it from Users → License.",
    )


async def decline(db: AsyncSession, tenant_id: uuid.UUID, request_id: uuid.UUID, admin_id: uuid.UUID, response: str) -> None:
    request = await _get(db, tenant_id, request_id)
    if request.status != "open":
        raise LicenseFileError("This request has already been answered or cancelled.", 409)
    request.status = "declined"
    request.resolved_at = datetime.now(timezone.utc)
    request.resolved_by = admin_id
    request.response = response.strip()
    await write_audit_log(db, tenant_id, None, "tenant.license_request_declined", "license_request", request.id, {"platform_admin_id": str(admin_id)})
    await _notify(db, request, "Your license request was declined", request.response)
