"""Client-area endpoints for cPanel account self-service."""
from __future__ import annotations

import logging
import time
from typing import Dict, List, Tuple

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

from core.database import get_db
from dependencies.auth import get_current_user
from models.cpanel import (
    CpanelAccountUsage,
    CpanelClientAccount,
    CpanelPackage,
    CpanelServer,
)
from schemas.auth import UserResponse
from schemas.cpanel import (
    CpanelAccountDetail,
    CpanelAccountOut,
    CpanelAccountUsageOut,
    CpanelSsoResult,
    EmailAccountIn,
    FtpAccountIn,
    MysqlDatabaseIn,
    MysqlUserIn,
    QuickActionResult,
    SimpleItemOut,
    SubdomainIn,
)
from services.cpanel.client import CpanelApi, CpanelApiError
from services.cpanel.provisioning import sso_login_url, sync_usage

logger = logging.getLogger(__name__)

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


# Simple per-user sliding-window rate limiter.
_RATE_WINDOW_SECS = 60.0
_RATE_MAX_CALLS = 30
_rate_bucket: Dict[str, List[float]] = {}


def _rate_limit(user_id: str) -> None:
    now = time.monotonic()
    hits = [t for t in _rate_bucket.get(user_id, []) if now - t < _RATE_WINDOW_SECS]
    if len(hits) >= _RATE_MAX_CALLS:
        raise HTTPException(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail="rate_limited")
    hits.append(now)
    _rate_bucket[user_id] = hits


async def _load_owned_account(
    db: AsyncSession, user: UserResponse, account_id: int
) -> Tuple[CpanelClientAccount, CpanelServer]:
    account = (
        await db.execute(select(CpanelClientAccount).where(CpanelClientAccount.id == account_id))
    ).scalar_one_or_none()
    if not account or account.user_id != user.id:
        raise HTTPException(status_code=404, detail="account_not_found")
    if account.status in {"terminated", "failed"}:
        raise HTTPException(status_code=409, detail=f"account_{account.status}")
    server = (
        await db.execute(select(CpanelServer).where(CpanelServer.id == account.server_id))
    ).scalar_one_or_none()
    if not server:
        raise HTTPException(status_code=500, detail="server_missing")
    return account, server


@router.get("/accounts", response_model=List[CpanelAccountOut])
async def my_accounts(
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_current_user),
) -> List[CpanelClientAccount]:
    rows = (
        await db.execute(
            select(CpanelClientAccount)
            .where(CpanelClientAccount.user_id == current_user.id)
            .order_by(CpanelClientAccount.id.desc())
        )
    ).scalars().all()
    return list(rows)


@router.get("/accounts/{account_id}", response_model=CpanelAccountDetail)
async def my_account_detail(
    account_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_current_user),
) -> CpanelAccountDetail:
    account, server = await _load_owned_account(db, current_user, account_id)
    pkg = None
    if account.package_id:
        pkg = (
            await db.execute(select(CpanelPackage).where(CpanelPackage.id == account.package_id))
        ).scalar_one_or_none()
    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
    detail.package_name = pkg.display_name if pkg else None
    if usage:
        detail.usage = CpanelAccountUsageOut.model_validate(usage)
    return detail


@router.post("/accounts/{account_id}/sync-usage", response_model=CpanelAccountUsageOut)
async def sync_my_usage(
    account_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_current_user),
) -> CpanelAccountUsageOut:
    _rate_limit(current_user.id)
    account, _server = await _load_owned_account(db, current_user, account_id)
    try:
        usage = await sync_usage(db, account)
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    return CpanelAccountUsageOut.model_validate(usage)


@router.post("/accounts/{account_id}/sso", response_model=CpanelSsoResult)
async def client_sso(
    account_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_current_user),
) -> CpanelSsoResult:
    _rate_limit(current_user.id)
    account, _server = await _load_owned_account(db, current_user, account_id)
    try:
        url = await sso_login_url(db, account)
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    return CpanelSsoResult(url=url)


# ---------------------------------------------------------------------------
# Quick actions
# ---------------------------------------------------------------------------
def _items_from_uapi(resp: Dict, keys: Tuple[str, ...]) -> List[SimpleItemOut]:
    payload = resp.get("result") if isinstance(resp.get("result"), dict) else resp
    data = (payload or {}).get("data") if isinstance(payload, dict) else None
    out: List[SimpleItemOut] = []
    if isinstance(data, list):
        for item in data:
            if isinstance(item, dict):
                name_val = next((str(item.get(k)) for k in keys if item.get(k)), "")
                if name_val:
                    out.append(SimpleItemOut(name=name_val))
            elif isinstance(item, str):
                out.append(SimpleItemOut(name=item))
    return out


# Email ---------------------------------------------------------------------
@router.get("/accounts/{account_id}/emails", response_model=List[SimpleItemOut])
async def list_emails(
    account_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_current_user),
) -> List[SimpleItemOut]:
    _rate_limit(current_user.id)
    account, server = await _load_owned_account(db, current_user, account_id)
    try:
        resp = await CpanelApi(server, db=db).uapi(
            cpanel_user=account.cpanel_username, module="Email", function="list_pops", account_id=account.id,
        )
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    return _items_from_uapi(resp, ("email", "login", "user"))


@router.post("/accounts/{account_id}/emails", response_model=QuickActionResult, status_code=status.HTTP_201_CREATED)
async def create_email(
    account_id: int,
    payload: EmailAccountIn,
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_current_user),
) -> QuickActionResult:
    _rate_limit(current_user.id)
    account, server = await _load_owned_account(db, current_user, account_id)
    if "@" not in payload.email:
        raise HTTPException(status_code=400, detail="invalid_email")
    local, _, domain = payload.email.partition("@")
    try:
        await CpanelApi(server, db=db).uapi(
            cpanel_user=account.cpanel_username,
            module="Email",
            function="add_pop",
            params={"email": local, "domain": domain, "password": payload.password, "quota": payload.quota_mb},
            account_id=account.id,
        )
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    return QuickActionResult(ok=True, message="Email account created")


@router.delete("/accounts/{account_id}/emails/{email}", response_model=QuickActionResult)
async def delete_email(
    account_id: int,
    email: str,
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_current_user),
) -> QuickActionResult:
    _rate_limit(current_user.id)
    account, server = await _load_owned_account(db, current_user, account_id)
    if "@" not in email:
        raise HTTPException(status_code=400, detail="invalid_email")
    local, _, domain = email.partition("@")
    try:
        await CpanelApi(server, db=db).uapi(
            cpanel_user=account.cpanel_username,
            module="Email",
            function="delete_pop",
            params={"email": local, "domain": domain},
            account_id=account.id,
        )
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    return QuickActionResult(ok=True, message="Email account deleted")


# FTP -----------------------------------------------------------------------
@router.get("/accounts/{account_id}/ftp", response_model=List[SimpleItemOut])
async def list_ftp(
    account_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_current_user),
) -> List[SimpleItemOut]:
    _rate_limit(current_user.id)
    account, server = await _load_owned_account(db, current_user, account_id)
    try:
        resp = await CpanelApi(server, db=db).uapi(
            cpanel_user=account.cpanel_username, module="Ftp", function="list_ftp", account_id=account.id,
        )
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    return _items_from_uapi(resp, ("user", "login"))


@router.post("/accounts/{account_id}/ftp", response_model=QuickActionResult, status_code=status.HTTP_201_CREATED)
async def create_ftp(
    account_id: int,
    payload: FtpAccountIn,
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_current_user),
) -> QuickActionResult:
    _rate_limit(current_user.id)
    account, server = await _load_owned_account(db, current_user, account_id)
    params: Dict[str, object] = {"user": payload.username, "pass": payload.password, "quota": payload.quota_mb}
    if payload.homedir:
        params["homedir"] = payload.homedir
    try:
        await CpanelApi(server, db=db).uapi(
            cpanel_user=account.cpanel_username,
            module="Ftp",
            function="add_ftp",
            params=params,
            account_id=account.id,
        )
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    return QuickActionResult(ok=True, message="FTP account created")


@router.delete("/accounts/{account_id}/ftp/{username}", response_model=QuickActionResult)
async def delete_ftp(
    account_id: int,
    username: str,
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_current_user),
) -> QuickActionResult:
    _rate_limit(current_user.id)
    account, server = await _load_owned_account(db, current_user, account_id)
    try:
        await CpanelApi(server, db=db).uapi(
            cpanel_user=account.cpanel_username,
            module="Ftp",
            function="delete_ftp",
            params={"user": username},
            account_id=account.id,
        )
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    return QuickActionResult(ok=True, message="FTP account deleted")


# MySQL ---------------------------------------------------------------------
@router.get("/accounts/{account_id}/mysql/databases", response_model=List[SimpleItemOut])
async def list_databases(
    account_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_current_user),
) -> List[SimpleItemOut]:
    _rate_limit(current_user.id)
    account, server = await _load_owned_account(db, current_user, account_id)
    try:
        resp = await CpanelApi(server, db=db).uapi(
            cpanel_user=account.cpanel_username, module="Mysql", function="list_databases", account_id=account.id,
        )
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    return _items_from_uapi(resp, ("database", "name"))


@router.post(
    "/accounts/{account_id}/mysql/databases",
    response_model=QuickActionResult,
    status_code=status.HTTP_201_CREATED,
)
async def create_database(
    account_id: int,
    payload: MysqlDatabaseIn,
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_current_user),
) -> QuickActionResult:
    _rate_limit(current_user.id)
    account, server = await _load_owned_account(db, current_user, account_id)
    try:
        await CpanelApi(server, db=db).uapi(
            cpanel_user=account.cpanel_username,
            module="Mysql",
            function="create_database",
            params={"name": payload.db_name},
            account_id=account.id,
        )
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    return QuickActionResult(ok=True, message="Database created")


@router.delete("/accounts/{account_id}/mysql/databases/{db_name}", response_model=QuickActionResult)
async def delete_database(
    account_id: int,
    db_name: str,
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_current_user),
) -> QuickActionResult:
    _rate_limit(current_user.id)
    account, server = await _load_owned_account(db, current_user, account_id)
    try:
        await CpanelApi(server, db=db).uapi(
            cpanel_user=account.cpanel_username,
            module="Mysql",
            function="delete_database",
            params={"name": db_name},
            account_id=account.id,
        )
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    return QuickActionResult(ok=True, message="Database deleted")


@router.post("/accounts/{account_id}/mysql/users", response_model=QuickActionResult, status_code=status.HTTP_201_CREATED)
async def create_db_user(
    account_id: int,
    payload: MysqlUserIn,
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_current_user),
) -> QuickActionResult:
    _rate_limit(current_user.id)
    account, server = await _load_owned_account(db, current_user, account_id)
    try:
        await CpanelApi(server, db=db).uapi(
            cpanel_user=account.cpanel_username,
            module="Mysql",
            function="create_user",
            params={"name": payload.username, "password": payload.password},
            account_id=account.id,
        )
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    return QuickActionResult(ok=True, message="DB user created")


# Subdomains ----------------------------------------------------------------
@router.get("/accounts/{account_id}/subdomains", response_model=List[SimpleItemOut])
async def list_subdomains(
    account_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_current_user),
) -> List[SimpleItemOut]:
    _rate_limit(current_user.id)
    account, server = await _load_owned_account(db, current_user, account_id)
    try:
        resp = await CpanelApi(server, db=db).uapi(
            cpanel_user=account.cpanel_username,
            module="SubDomain",
            function="list_subdomains",
            account_id=account.id,
        )
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    return _items_from_uapi(resp, ("subdomain", "domain"))


@router.post(
    "/accounts/{account_id}/subdomains",
    response_model=QuickActionResult,
    status_code=status.HTTP_201_CREATED,
)
async def create_subdomain(
    account_id: int,
    payload: SubdomainIn,
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_current_user),
) -> QuickActionResult:
    _rate_limit(current_user.id)
    account, server = await _load_owned_account(db, current_user, account_id)
    params: Dict[str, object] = {"domain": payload.subdomain, "rootdomain": payload.parent_domain}
    if payload.docroot:
        params["dir"] = payload.docroot
    try:
        await CpanelApi(server, db=db).uapi(
            cpanel_user=account.cpanel_username,
            module="SubDomain",
            function="addsubdomain",
            params=params,
            account_id=account.id,
        )
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    return QuickActionResult(ok=True, message="Subdomain created")


# SSL status / backups ------------------------------------------------------
@router.get("/accounts/{account_id}/ssl", response_model=List[SimpleItemOut])
async def list_ssl(
    account_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_current_user),
) -> List[SimpleItemOut]:
    _rate_limit(current_user.id)
    account, server = await _load_owned_account(db, current_user, account_id)
    try:
        resp = await CpanelApi(server, db=db).uapi(
            cpanel_user=account.cpanel_username,
            module="SSL",
            function="installed_hosts",
            account_id=account.id,
        )
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    return _items_from_uapi(resp, ("servername", "domain"))


@router.get("/accounts/{account_id}/backups", response_model=List[SimpleItemOut])
async def list_backups(
    account_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_current_user),
) -> List[SimpleItemOut]:
    _rate_limit(current_user.id)
    account, server = await _load_owned_account(db, current_user, account_id)
    try:
        resp = await CpanelApi(server, db=db).uapi(
            cpanel_user=account.cpanel_username,
            module="Backup",
            function="list_system_backups",
            account_id=account.id,
        )
    except CpanelApiError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    return _items_from_uapi(resp, ("filename", "name"))