"""Admin endpoints for the WHM/cPanel Reseller Manager."""
from __future__ import annotations

import logging
from datetime import datetime, timezone
from typing import List, Optional

from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from core.database import get_db
from core.mask_crypto import encrypt_text
from dependencies.auth import get_admin_user
from models.cpanel import (
    CpanelApiLog,
    CpanelAuditLog,
    CpanelClientAccount,
    CpanelPackage,
    CpanelServer,
)
from schemas.auth import UserResponse
from schemas.cpanel import (
    CpanelAccountActionResult,
    CpanelAccountAdminCreate,
    CpanelAccountDetail,
    CpanelAccountOut,
    CpanelApiLogOut,
    CpanelAuditLogOut,
    CpanelChangePackageIn,
    CpanelOrphanAccount,
    CpanelOrphanLinkIn,
    CpanelPackageCreate,
    CpanelPackageOut,
    CpanelPackageSyncResult,
    CpanelPackageUpdate,
    CpanelServerCreate,
    CpanelServerOut,
    CpanelServerTestResult,
    CpanelServerUpdate,
    CpanelSsoResult,
)
from services.cpanel.client import CpanelApi, CpanelApiError, probe_server
from services.cpanel.provisioning import (
    change_package as svc_change_package,
    provision_account,
    sso_login_url,
    suspend_account as svc_suspend,
    terminate_account as svc_terminate,
    unsuspend_account as svc_unsuspend,
)

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/v1/admin/cpanel", tags=["cpanel-admin"])


# ---------------------------------------------------------------------------
# Servers
# ---------------------------------------------------------------------------
@router.get("/servers", response_model=List[CpanelServerOut])
async def list_servers(
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> List[CpanelServer]:
    rows = (await db.execute(select(CpanelServer).order_by(CpanelServer.id))).scalars().all()
    return list(rows)


@router.post("/servers", response_model=CpanelServerOut, status_code=status.HTTP_201_CREATED)
async def create_server(
    payload: CpanelServerCreate,
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> CpanelServer:
    server = CpanelServer(
        name=payload.name,
        hostname=payload.hostname,
        port=payload.port,
        username=payload.username,
        encrypted_api_token=encrypt_text(payload.api_token),
        verify_tls=payload.verify_tls,
        is_active=payload.is_active,
        notes=payload.notes,
    )
    db.add(server)
    await db.commit()
    await db.refresh(server)
    return server


@router.patch("/servers/{server_id}", response_model=CpanelServerOut)
async def update_server(
    server_id: int,
    payload: CpanelServerUpdate,
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> CpanelServer:
    server = (await db.execute(select(CpanelServer).where(CpanelServer.id == server_id))).scalar_one_or_none()
    if not server:
        raise HTTPException(status_code=404, detail="server_not_found")

    data = payload.model_dump(exclude_unset=True)
    if "api_token" in data and data["api_token"]:
        server.encrypted_api_token = encrypt_text(data.pop("api_token"))
    else:
        data.pop("api_token", None)
    for key, value in data.items():
        setattr(server, key, value)
    await db.commit()
    await db.refresh(server)
    return server


@router.delete("/servers/{server_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_server(
    server_id: int,
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> None:
    server = (await db.execute(select(CpanelServer).where(CpanelServer.id == server_id))).scalar_one_or_none()
    if not server:
        raise HTTPException(status_code=404, detail="server_not_found")
    # Refuse deletion if accounts exist
    attached = (
        await db.execute(select(CpanelClientAccount.id).where(CpanelClientAccount.server_id == server_id).limit(1))
    ).scalar_one_or_none()
    if attached:
        raise HTTPException(status_code=409, detail="server_has_accounts")
    await db.delete(server)
    await db.commit()


@router.post("/servers/{server_id}/test", response_model=CpanelServerTestResult)
async def test_server(
    server_id: int,
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> CpanelServerTestResult:
    server = (await db.execute(select(CpanelServer).where(CpanelServer.id == server_id))).scalar_one_or_none()
    if not server:
        raise HTTPException(status_code=404, detail="server_not_found")
    result = await probe_server(server, db=db)
    server.last_tested_at = datetime.now(timezone.utc)
    server.last_test_status = "ok" if result["ok"] else "failed"
    server.last_test_message = result.get("message")
    await db.commit()
    return CpanelServerTestResult(
        ok=bool(result["ok"]), message=result.get("message", ""), whm_version=result.get("version")
    )


# ---------------------------------------------------------------------------
# Packages
# ---------------------------------------------------------------------------
@router.get("/packages", response_model=List[CpanelPackageOut])
async def list_packages(
    server_id: Optional[int] = Query(None),
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> List[CpanelPackage]:
    stmt = select(CpanelPackage).order_by(CpanelPackage.sort_order, CpanelPackage.id)
    if server_id is not None:
        stmt = stmt.where(CpanelPackage.server_id == server_id)
    return list((await db.execute(stmt)).scalars().all())


@router.post("/packages", response_model=CpanelPackageOut, status_code=status.HTTP_201_CREATED)
async def create_package(
    payload: CpanelPackageCreate,
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> CpanelPackage:
    pkg = CpanelPackage(**payload.model_dump())
    db.add(pkg)
    await db.commit()
    await db.refresh(pkg)
    return pkg


@router.patch("/packages/{package_id}", response_model=CpanelPackageOut)
async def update_package(
    package_id: int,
    payload: CpanelPackageUpdate,
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> CpanelPackage:
    pkg = (await db.execute(select(CpanelPackage).where(CpanelPackage.id == package_id))).scalar_one_or_none()
    if not pkg:
        raise HTTPException(status_code=404, detail="package_not_found")
    for key, value in payload.model_dump(exclude_unset=True).items():
        setattr(pkg, key, value)
    await db.commit()
    await db.refresh(pkg)
    return pkg


@router.delete("/packages/{package_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_package(
    package_id: int,
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> None:
    pkg = (await db.execute(select(CpanelPackage).where(CpanelPackage.id == package_id))).scalar_one_or_none()
    if not pkg:
        raise HTTPException(status_code=404, detail="package_not_found")
    await db.delete(pkg)
    await db.commit()


@router.post("/packages/sync/{server_id}", response_model=CpanelPackageSyncResult)
async def sync_packages_from_whm(
    server_id: int,
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> CpanelPackageSyncResult:
    server = (await db.execute(select(CpanelServer).where(CpanelServer.id == server_id))).scalar_one_or_none()
    if not server:
        raise HTTPException(status_code=404, detail="server_not_found")

    api = CpanelApi(server, db=db)
    try:
        raw = await api.list_packages()
    except CpanelApiError as exc:
        return CpanelPackageSyncResult(ok=False, message=str(exc))

    data = raw.get("data") if isinstance(raw.get("data"), dict) else {}
    packages = (data.get("pkg") or []) if isinstance(data, dict) else []
    existing = {
        p.whm_package_name: p
        for p in (
            await db.execute(select(CpanelPackage).where(CpanelPackage.server_id == server_id))
        ).scalars().all()
    }
    imported = 0
    updated = 0
    names: List[str] = []
    for item in packages:
        if not isinstance(item, dict):
            continue
        name = str(item.get("name") or "").strip()
        if not name:
            continue
        names.append(name)
        quota = item.get("QUOTA") or item.get("quota")
        bw = item.get("BWLIMIT") or item.get("bwlimit")

        def _to_int(val):
            try:
                return int(str(val).strip())
            except Exception:  # noqa: BLE001
                return None

        if name in existing:
            pkg = existing[name]
            pkg.disk_quota_mb = _to_int(quota) or pkg.disk_quota_mb
            pkg.bandwidth_mb = _to_int(bw) or pkg.bandwidth_mb
            updated += 1
        else:
            pkg = CpanelPackage(
                server_id=server_id,
                display_name=name,
                slug=f"srv{server_id}-{name.lower().replace(' ', '-')}"[:150],
                whm_package_name=name,
                disk_quota_mb=_to_int(quota),
                bandwidth_mb=_to_int(bw),
                is_active=True,
            )
            db.add(pkg)
            imported += 1
    await db.commit()
    return CpanelPackageSyncResult(ok=True, imported=imported, updated=updated, items=names)


# ---------------------------------------------------------------------------
# Accounts
# ---------------------------------------------------------------------------
@router.get("/accounts", response_model=List[CpanelAccountOut])
async def list_accounts(
    status_filter: Optional[str] = Query(None, alias="status"),
    server_id: Optional[int] = Query(None),
    limit: int = Query(200, ge=1, le=1000),
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> List[CpanelClientAccount]:
    stmt = select(CpanelClientAccount).order_by(CpanelClientAccount.id.desc()).limit(limit)
    if status_filter:
        stmt = stmt.where(CpanelClientAccount.status == status_filter)
    if server_id is not None:
        stmt = stmt.where(CpanelClientAccount.server_id == server_id)
    return list((await db.execute(stmt)).scalars().all())


@router.get("/accounts/{account_id}", response_model=CpanelAccountDetail)
async def get_account(
    account_id: int,
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> CpanelAccountDetail:
    account = (
        await db.execute(select(CpanelClientAccount).where(CpanelClientAccount.id == account_id))
    ).scalar_one_or_none()
    if not account:
        raise HTTPException(status_code=404, detail="account_not_found")
    server = (
        await db.execute(select(CpanelServer).where(CpanelServer.id == account.server_id))
    ).scalar_one_or_none()
    pkg = (
        await db.execute(select(CpanelPackage).where(CpanelPackage.id == account.package_id))
    ).scalar_one_or_none() if account.package_id else None
    from models.cpanel import CpanelAccountUsage  # local import to avoid cycle
    usage = (
        await db.execute(select(CpanelAccountUsage).where(CpanelAccountUsage.account_id == account.id))
    ).scalar_one_or_none()

    detail = CpanelAccountDetail.model_validate(account)
    detail.server_hostname = server.hostname if server else None
    detail.package_name = pkg.display_name if pkg else None
    if usage:
        from schemas.cpanel import CpanelAccountUsageOut
        detail.usage = CpanelAccountUsageOut.model_validate(usage)
    return detail


@router.post("/accounts", response_model=CpanelAccountActionResult, status_code=status.HTTP_201_CREATED)
async def admin_create_account(
    payload: CpanelAccountAdminCreate,
    db: AsyncSession = Depends(get_db),
    admin: UserResponse = Depends(get_admin_user),
) -> CpanelAccountActionResult:
    try:
        account, _pwd = await provision_account(
            db,
            user_id=payload.user_id,
            server_id=payload.server_id,
            package_id=payload.package_id,
            main_domain=payload.main_domain,
            contact_email=payload.contact_email,
            whmcs_service_id=payload.whmcs_service_id,
            whmcs_client_id=payload.whmcs_client_id,
            admin_user_id=admin.id,
            admin_email=admin.email,
        )
    except (CpanelApiError, ValueError) as exc:
        raise HTTPException(status_code=400, detail=str(exc))
    return CpanelAccountActionResult(ok=True, message="Account provisioned", account=CpanelAccountOut.model_validate(account))


def _lifecycle_target(db: AsyncSession):
    async def _inner(account_id: int) -> CpanelClientAccount:
        account = (
            await db.execute(select(CpanelClientAccount).where(CpanelClientAccount.id == account_id))
        ).scalar_one_or_none()
        if not account:
            raise HTTPException(status_code=404, detail="account_not_found")
        return account
    return _inner


@router.post("/accounts/{account_id}/suspend", response_model=CpanelAccountActionResult)
async def admin_suspend(
    account_id: int,
    reason: Optional[str] = Query(None, max_length=255),
    db: AsyncSession = Depends(get_db),
    admin: UserResponse = Depends(get_admin_user),
) -> CpanelAccountActionResult:
    account = (
        await db.execute(select(CpanelClientAccount).where(CpanelClientAccount.id == account_id))
    ).scalar_one_or_none()
    if not account:
        raise HTTPException(status_code=404, detail="account_not_found")
    try:
        account = await svc_suspend(db, account, reason=reason, admin_user_id=admin.id, admin_email=admin.email)
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    return CpanelAccountActionResult(ok=True, message="Account suspended", account=CpanelAccountOut.model_validate(account))


@router.post("/accounts/{account_id}/unsuspend", response_model=CpanelAccountActionResult)
async def admin_unsuspend(
    account_id: int,
    db: AsyncSession = Depends(get_db),
    admin: UserResponse = Depends(get_admin_user),
) -> CpanelAccountActionResult:
    account = (
        await db.execute(select(CpanelClientAccount).where(CpanelClientAccount.id == account_id))
    ).scalar_one_or_none()
    if not account:
        raise HTTPException(status_code=404, detail="account_not_found")
    try:
        account = await svc_unsuspend(db, account, admin_user_id=admin.id, admin_email=admin.email)
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    return CpanelAccountActionResult(ok=True, message="Account unsuspended", account=CpanelAccountOut.model_validate(account))


@router.post("/accounts/{account_id}/terminate", response_model=CpanelAccountActionResult)
async def admin_terminate(
    account_id: int,
    db: AsyncSession = Depends(get_db),
    admin: UserResponse = Depends(get_admin_user),
) -> CpanelAccountActionResult:
    account = (
        await db.execute(select(CpanelClientAccount).where(CpanelClientAccount.id == account_id))
    ).scalar_one_or_none()
    if not account:
        raise HTTPException(status_code=404, detail="account_not_found")
    try:
        account = await svc_terminate(db, account, admin_user_id=admin.id, admin_email=admin.email)
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    return CpanelAccountActionResult(ok=True, message="Account terminated", account=CpanelAccountOut.model_validate(account))


@router.post("/accounts/{account_id}/change-package", response_model=CpanelAccountActionResult)
async def admin_change_package(
    account_id: int,
    payload: CpanelChangePackageIn,
    db: AsyncSession = Depends(get_db),
    admin: UserResponse = Depends(get_admin_user),
) -> CpanelAccountActionResult:
    account = (
        await db.execute(select(CpanelClientAccount).where(CpanelClientAccount.id == account_id))
    ).scalar_one_or_none()
    if not account:
        raise HTTPException(status_code=404, detail="account_not_found")
    try:
        account = await svc_change_package(
            db, account, payload.new_package_id, admin_user_id=admin.id, admin_email=admin.email
        )
    except (CpanelApiError, ValueError) as exc:
        raise HTTPException(status_code=400, detail=str(exc))
    return CpanelAccountActionResult(ok=True, message="Package changed", account=CpanelAccountOut.model_validate(account))


@router.post("/accounts/{account_id}/sso", response_model=CpanelSsoResult)
async def admin_sso(
    account_id: int,
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> CpanelSsoResult:
    account = (
        await db.execute(select(CpanelClientAccount).where(CpanelClientAccount.id == account_id))
    ).scalar_one_or_none()
    if not account:
        raise HTTPException(status_code=404, detail="account_not_found")
    try:
        url = await sso_login_url(db, account)
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    return CpanelSsoResult(url=url)


# ---------------------------------------------------------------------------
# Orphan detection
# ---------------------------------------------------------------------------
@router.get("/orphans/{server_id}", response_model=List[CpanelOrphanAccount])
async def list_orphans(
    server_id: int,
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> List[CpanelOrphanAccount]:
    server = (
        await db.execute(select(CpanelServer).where(CpanelServer.id == server_id))
    ).scalar_one_or_none()
    if not server:
        raise HTTPException(status_code=404, detail="server_not_found")
    api = CpanelApi(server, db=db)
    try:
        data = await api.list_accounts()
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))

    accts = ((data.get("data") or {}).get("acct") or []) if isinstance(data.get("data"), dict) else []
    known = {
        row.cpanel_username
        for row in (
            await db.execute(
                select(CpanelClientAccount).where(CpanelClientAccount.server_id == server_id)
            )
        ).scalars().all()
    }
    out: List[CpanelOrphanAccount] = []
    for acct in accts:
        if not isinstance(acct, dict):
            continue
        user = str(acct.get("user") or "")
        if not user or user in known:
            continue
        out.append(
            CpanelOrphanAccount(
                cpanel_username=user,
                main_domain=str(acct.get("domain") or ""),
                package=str(acct.get("plan") or "") or None,
                server_id=server_id,
            )
        )
    return out


@router.post("/orphans/link", response_model=CpanelAccountOut, status_code=status.HTTP_201_CREATED)
async def link_orphan(
    payload: CpanelOrphanLinkIn,
    db: AsyncSession = Depends(get_db),
    admin: UserResponse = Depends(get_admin_user),
) -> CpanelClientAccount:
    account = CpanelClientAccount(
        user_id=payload.user_id,
        server_id=payload.server_id,
        package_id=payload.package_id,
        whmcs_service_id=payload.whmcs_service_id,
        whmcs_client_id=payload.whmcs_client_id,
        cpanel_username=payload.cpanel_username,
        main_domain=payload.main_domain,
        status="active",
        provisioned_at=datetime.now(timezone.utc),
    )
    db.add(account)
    await db.commit()
    await db.refresh(account)

    audit = CpanelAuditLog(
        action="cpanel.orphan_link",
        admin_user_id=admin.id,
        admin_email=admin.email,
        account_id=account.id,
        target_user_id=payload.user_id,
        details=f"{payload.cpanel_username}@server{payload.server_id}",
    )
    db.add(audit)
    await db.commit()
    return account


# ---------------------------------------------------------------------------
# Logs
# ---------------------------------------------------------------------------
@router.get("/api-logs", response_model=List[CpanelApiLogOut])
async def list_api_logs(
    server_id: Optional[int] = Query(None),
    status_filter: Optional[str] = Query(None, alias="status"),
    limit: int = Query(200, ge=1, le=1000),
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> List[CpanelApiLog]:
    stmt = select(CpanelApiLog).order_by(CpanelApiLog.id.desc()).limit(limit)
    if server_id is not None:
        stmt = stmt.where(CpanelApiLog.server_id == server_id)
    if status_filter:
        stmt = stmt.where(CpanelApiLog.status == status_filter)
    return list((await db.execute(stmt)).scalars().all())


@router.get("/audit-log", response_model=List[CpanelAuditLogOut])
async def list_audit_log(
    limit: int = Query(200, ge=1, le=1000),
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> List[CpanelAuditLog]:
    stmt = select(CpanelAuditLog).order_by(CpanelAuditLog.id.desc()).limit(limit)
    return list((await db.execute(stmt)).scalars().all())