"""Provisioning lifecycle helpers for cPanel accounts."""
from __future__ import annotations

import logging
import secrets
import string
from datetime import datetime, timezone
from typing import Optional, Tuple

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

from core.mask_crypto import encrypt_text
from models.cpanel import (
    CpanelAccountUsage,
    CpanelAuditLog,
    CpanelClientAccount,
    CpanelPackage,
    CpanelServer,
)
from services.cpanel.client import CpanelApi, CpanelApiError

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# Utilities
# ---------------------------------------------------------------------------
def generate_password(length: int = 18) -> str:
    """Generate a strong random password without easily confused characters."""
    alphabet = string.ascii_letters + string.digits + "!@#%+-="
    # Guarantee at least one of each class so WHM's strength check is happy.
    base = [
        secrets.choice(string.ascii_lowercase),
        secrets.choice(string.ascii_uppercase),
        secrets.choice(string.digits),
        secrets.choice("!@#%+-="),
    ]
    remaining = [secrets.choice(alphabet) for _ in range(max(length - len(base), 4))]
    pool = base + remaining
    secrets.SystemRandom().shuffle(pool)
    return "".join(pool)


def derive_username(domain: str, user_id: str) -> str:
    """Derive a safe, unique-enough cPanel username from a domain."""
    prefix = "".join(ch for ch in domain.split(".")[0].lower() if ch.isalnum())[:6] or "usr"
    suffix = secrets.token_hex(2)
    candidate = f"{prefix}{suffix}"
    # cPanel usernames must start with a letter
    if not candidate[0].isalpha():
        candidate = "u" + candidate[1:]
    return candidate[:16]


async def _get_server(db: AsyncSession, server_id: int) -> CpanelServer:
    server = (await db.execute(select(CpanelServer).where(CpanelServer.id == server_id))).scalar_one_or_none()
    if not server:
        raise ValueError(f"cPanel server {server_id} not found")
    return server


async def _get_package(db: AsyncSession, package_id: int) -> CpanelPackage:
    pkg = (await db.execute(select(CpanelPackage).where(CpanelPackage.id == package_id))).scalar_one_or_none()
    if not pkg:
        raise ValueError(f"cPanel package {package_id} not found")
    return pkg


async def _audit(
    db: AsyncSession,
    *,
    action: str,
    account: Optional[CpanelClientAccount] = None,
    admin_user_id: Optional[str] = None,
    admin_email: Optional[str] = None,
    details: Optional[str] = None,
) -> None:
    try:
        row = CpanelAuditLog(
            action=action,
            admin_user_id=admin_user_id,
            admin_email=admin_email,
            account_id=account.id if account else None,
            target_user_id=account.user_id if account else None,
            details=details,
        )
        db.add(row)
        await db.commit()
    except Exception:  # noqa: BLE001
        logger.exception("[cpanel] audit log failed")
        await db.rollback()


# ---------------------------------------------------------------------------
# Lifecycle actions
# ---------------------------------------------------------------------------
async def provision_account(
    db: AsyncSession,
    *,
    user_id: str,
    server_id: int,
    package_id: int,
    main_domain: str,
    contact_email: Optional[str] = None,
    whmcs_service_id: Optional[int] = None,
    whmcs_client_id: Optional[int] = None,
    admin_user_id: Optional[str] = None,
    admin_email: Optional[str] = None,
) -> Tuple[CpanelClientAccount, str]:
    """Create a new cPanel account on WHM and persist the mirror row.

    Returns ``(account, plain_password)``.  The plain password is returned
    so the caller can email it to the client; only the encrypted form is
    stored in the database.
    """
    server = await _get_server(db, server_id)
    package = await _get_package(db, package_id)

    username = derive_username(main_domain, user_id)
    password = generate_password()

    account = CpanelClientAccount(
        user_id=user_id,
        server_id=server.id,
        package_id=package.id,
        whmcs_service_id=whmcs_service_id,
        whmcs_client_id=whmcs_client_id,
        cpanel_username=username,
        main_domain=main_domain,
        contact_email=contact_email,
        encrypted_password=encrypt_text(password),
        status="pending",
    )
    db.add(account)
    await db.commit()
    await db.refresh(account)

    api = CpanelApi(server, db=db)
    try:
        await api.create_account(
            username=username,
            domain=main_domain,
            password=password,
            package=package.whm_package_name,
            contact_email=contact_email,
            account_id=account.id,
        )
    except CpanelApiError as exc:
        account.status = "failed"
        account.last_error = str(exc)[:500]
        await db.commit()
        await _audit(db, action="cpanel.create_failed", account=account,
                     admin_user_id=admin_user_id, admin_email=admin_email, details=str(exc))
        raise

    account.status = "active"
    account.provisioned_at = datetime.now(timezone.utc)
    account.last_error = None
    await db.commit()
    await db.refresh(account)

    await _audit(
        db,
        action="cpanel.create",
        account=account,
        admin_user_id=admin_user_id,
        admin_email=admin_email,
        details=f"server={server.name} pkg={package.whm_package_name} domain={main_domain}",
    )
    return account, password


async def suspend_account(
    db: AsyncSession,
    account: CpanelClientAccount,
    reason: Optional[str] = None,
    *,
    admin_user_id: Optional[str] = None,
    admin_email: Optional[str] = None,
) -> CpanelClientAccount:
    server = await _get_server(db, account.server_id)
    api = CpanelApi(server, db=db)
    await api.suspend_account(account.cpanel_username, reason=reason, account_id=account.id)
    account.status = "suspended"
    account.suspend_reason = reason
    account.suspended_at = datetime.now(timezone.utc)
    await db.commit()
    await db.refresh(account)
    await _audit(db, action="cpanel.suspend", account=account,
                 admin_user_id=admin_user_id, admin_email=admin_email, details=reason)
    return account


async def unsuspend_account(
    db: AsyncSession,
    account: CpanelClientAccount,
    *,
    admin_user_id: Optional[str] = None,
    admin_email: Optional[str] = None,
) -> CpanelClientAccount:
    server = await _get_server(db, account.server_id)
    api = CpanelApi(server, db=db)
    await api.unsuspend_account(account.cpanel_username, account_id=account.id)
    account.status = "active"
    account.suspend_reason = None
    account.unsuspended_at = datetime.now(timezone.utc)
    await db.commit()
    await db.refresh(account)
    await _audit(db, action="cpanel.unsuspend", account=account,
                 admin_user_id=admin_user_id, admin_email=admin_email)
    return account


async def terminate_account(
    db: AsyncSession,
    account: CpanelClientAccount,
    *,
    admin_user_id: Optional[str] = None,
    admin_email: Optional[str] = None,
) -> CpanelClientAccount:
    server = await _get_server(db, account.server_id)
    api = CpanelApi(server, db=db)
    await api.terminate_account(account.cpanel_username, account_id=account.id)
    account.status = "terminated"
    account.terminated_at = datetime.now(timezone.utc)
    await db.commit()
    await db.refresh(account)
    await _audit(db, action="cpanel.terminate", account=account,
                 admin_user_id=admin_user_id, admin_email=admin_email)
    return account


async def change_package(
    db: AsyncSession,
    account: CpanelClientAccount,
    new_package_id: int,
    *,
    admin_user_id: Optional[str] = None,
    admin_email: Optional[str] = None,
) -> CpanelClientAccount:
    server = await _get_server(db, account.server_id)
    new_pkg = await _get_package(db, new_package_id)
    api = CpanelApi(server, db=db)
    await api.change_package(account.cpanel_username, new_pkg.whm_package_name, account_id=account.id)
    account.package_id = new_pkg.id
    await db.commit()
    await db.refresh(account)
    await _audit(
        db,
        action="cpanel.change_package",
        account=account,
        admin_user_id=admin_user_id,
        admin_email=admin_email,
        details=f"new_package={new_pkg.whm_package_name}",
    )
    return account


async def sso_login_url(
    db: AsyncSession, account: CpanelClientAccount, service: str = "cpaneld"
) -> str:
    server = await _get_server(db, account.server_id)
    api = CpanelApi(server, db=db)
    data = await api.create_user_session(account.cpanel_username, service=service, account_id=account.id)
    payload = data.get("data") if isinstance(data.get("data"), dict) else data
    url = (payload or {}).get("url") if isinstance(payload, dict) else None
    if not url:
        # Some WHM versions return cp_security_token+session inside ``data``.
        url = f"https://{server.hostname}:2083/"
    return url


# ---------------------------------------------------------------------------
# Usage snapshot
# ---------------------------------------------------------------------------
async def sync_usage(db: AsyncSession, account: CpanelClientAccount) -> CpanelAccountUsage:
    """Fetch ``accountsummary`` and update the local usage row."""
    server = await _get_server(db, account.server_id)
    api = CpanelApi(server, db=db)
    try:
        data = await api.account_summary(account.cpanel_username)
    except CpanelApiError as exc:
        account.last_error = str(exc)[:500]
        await db.commit()
        raise

    acct_list = ((data.get("data") or {}).get("acct") if isinstance(data.get("data"), dict) else []) or []
    info = acct_list[0] if acct_list else {}

    def _to_int(val):
        try:
            return int(str(val).split()[0])
        except Exception:  # noqa: BLE001
            return None

    usage = (
        await db.execute(select(CpanelAccountUsage).where(CpanelAccountUsage.account_id == account.id))
    ).scalar_one_or_none()
    if not usage:
        usage = CpanelAccountUsage(account_id=account.id)
        db.add(usage)

    usage.disk_used_mb = _to_int(info.get("diskused"))
    usage.disk_limit_mb = _to_int(info.get("disklimit"))
    usage.bandwidth_used_mb = None  # requires a separate showbw call
    usage.bandwidth_limit_mb = None
    usage.ssl_status = "active" if info.get("has_ssl") else None
    usage.backup_status = "ok" if info.get("backup") else None
    usage.last_synced_at = datetime.now(timezone.utc)

    await db.commit()
    await db.refresh(usage)
    return usage