from datetime import datetime, timezone
import json
import logging

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

from app.core.crypto import decrypt_secret, encrypt_secret
from app.models.cloud_server import (
    CloudAccountType,
    CloudServer,
    CloudServerStatus,
    CloudServerType,
)
from app.redis_client import redis_client
from app.schemas.cloud_server import (
    CloudServerCreate,
    CloudServerTestRequest,
    CloudServerTestResult,
    CloudServerUpdate,
)
from app.services.dcim_cloud_client import (
    DcimCloudCredentials,
    status_cache_key,
    test_connection,
)
from app.services.zjmf_finance_client import (
    ZjmfFinanceCredentials,
    test_connection as test_zjmf_finance_connection,
)

log = logging.getLogger("qycw.cloud_server")


def _build_base_url(hostname: str, port: int, secure: bool) -> str:
    scheme = "https" if secure else "http"
    if port and port not in (80, 443):
        return f"{scheme}://{hostname}:{port}"
    return f"{scheme}://{hostname}"


def _validate_account_for_type(server_type: str, account_type: str) -> None:
    if server_type == CloudServerType.DCIMCLOUD.value:
        if account_type not in {CloudAccountType.ADMIN.value, CloudAccountType.AGENT.value}:
            raise HTTPException(status_code=400, detail="魔方云节点仅支持 admin/agent 账号类型")
        return
    if server_type == CloudServerType.ZJMF_FINANCE.value:
        if account_type not in {CloudAccountType.CLIENT.value, CloudAccountType.RESOURCE.value}:
            raise HTTPException(status_code=400, detail="魔方财务节点仅支持 client/resource 账号类型")
        return
    raise HTTPException(status_code=400, detail=f"未知上游类型：{server_type}")


async def list_servers(db: AsyncSession) -> list[CloudServer]:
    result = await db.execute(
        select(CloudServer).order_by(CloudServer.sort_order.asc(), CloudServer.id.desc())
    )
    return list(result.scalars().all())


async def get_server(db: AsyncSession, server_id: int) -> CloudServer:
    result = await db.execute(select(CloudServer).where(CloudServer.id == server_id))
    server = result.scalar_one_or_none()
    if not server:
        raise HTTPException(status_code=404, detail="服务器不存在")
    return server


async def create_server(db: AsyncSession, data: CloudServerCreate) -> CloudServer:
    server_type = CloudServerType(data.server_type).value
    account_type = CloudAccountType(data.account_type).value
    _validate_account_for_type(server_type, account_type)
    server = CloudServer(
        name=data.name,
        server_type=server_type,
        account_type=account_type,
        hostname=data.hostname,
        port=data.port,
        secure=data.secure,
        username=data.username,
        password_encrypted=encrypt_secret(data.password),
        client_id=data.client_id,
        user_prefix=data.user_prefix,
        disabled=data.disabled,
        remark=data.remark,
        sort_order=data.sort_order,
    )
    db.add(server)
    await db.flush()
    await db.refresh(server)
    return server


async def update_server(
    db: AsyncSession, server_id: int, data: CloudServerUpdate
) -> CloudServer:
    server = await get_server(db, server_id)
    update_data = data.model_dump(exclude_unset=True)

    if "password" in update_data:
        password = update_data.pop("password")
        if password:
            server.password_encrypted = encrypt_secret(password)

    if "account_type" in update_data:
        update_data["account_type"] = CloudAccountType(update_data["account_type"]).value
    if "server_type" in update_data:
        update_data["server_type"] = CloudServerType(update_data["server_type"]).value

    next_server_type = update_data.get("server_type", server.server_type)
    next_account_type = update_data.get("account_type", server.account_type)
    _validate_account_for_type(next_server_type, next_account_type)

    for key, value in update_data.items():
        setattr(server, key, value)

    await db.flush()
    await db.refresh(server)
    return server


async def delete_server(db: AsyncSession, server_id: int) -> None:
    server = await get_server(db, server_id)
    await db.delete(server)
    await db.flush()


async def test_saved_server(db: AsyncSession, server_id: int) -> CloudServerTestResult:
    server = await get_server(db, server_id)
    return await _run_check(db, server)


async def refresh_all_servers(db: AsyncSession) -> list[tuple[int, bool, str]]:
    """供定时任务调用：对所有未禁用的接口跑一次连通性测试。

    返回 [(server_id, ok, message), ...]，按列表顺序。失败不抛异常，逐个隔离。
    """
    result = await db.execute(
        select(CloudServer).where(CloudServer.disabled.is_(False)).order_by(CloudServer.id.asc())
    )
    servers = list(result.scalars().all())
    summary: list[tuple[int, bool, str]] = []
    for server in servers:
        try:
            res = await _run_check(db, server)
            summary.append((server.id, res.ok, res.message))
        except Exception as e:  # noqa: BLE001
            log.exception("refresh cloud_server[%s] failed", server.id)
            summary.append((server.id, False, f"未知错误：{e}"))
    await db.commit()
    return summary


async def _run_check(db: AsyncSession, server: CloudServer) -> CloudServerTestResult:
    """执行一次连通性测试并把结果写回 DB（status/last_check_*）+ Redis 状态键。"""
    if server.server_type == CloudServerType.ZJMF_FINANCE.value:
        creds = ZjmfFinanceCredentials(
            base_url=server.base_url,
            username=server.username,
            password=decrypt_secret(server.password_encrypted),
            account_type=server.account_type,
        )
        ok, message, latency = await test_zjmf_finance_connection(creds, server_id=server.id, force=True)
    else:
        creds = DcimCloudCredentials(
            base_url=server.base_url,
            username=server.username,
            password=decrypt_secret(server.password_encrypted),
            account_type=server.account_type,
        )
        ok, message, latency = await test_connection(creds, server_id=server.id, force=True)

    checked_at = datetime.now(timezone.utc)
    server.status = (CloudServerStatus.ONLINE if ok else CloudServerStatus.OFFLINE).value
    server.last_check_at = checked_at
    server.last_check_message = message[:500]
    await db.flush()

    payload = {
        "ok": ok,
        "message": message,
        "latency_ms": latency,
        "checked_at": checked_at.isoformat(),
        "base_url": server.base_url,
        "server_type": server.server_type,
        "account_type": server.account_type,
        "username": server.username,
        "user_prefix": server.user_prefix or "",
    }
    try:
        await redis_client.set(status_cache_key(server.id), json.dumps(payload, ensure_ascii=False))
    except Exception:
        log.warning("write redis status key failed for server %s", server.id, exc_info=True)

    return CloudServerTestResult(ok=ok, message=message, latency_ms=latency)


async def test_unsaved(data: CloudServerTestRequest) -> CloudServerTestResult:
    server_type = CloudServerType(data.server_type).value
    account_type = CloudAccountType(data.account_type).value
    _validate_account_for_type(server_type, account_type)
    base_url = _build_base_url(data.hostname, data.port, data.secure)
    if server_type == CloudServerType.ZJMF_FINANCE.value:
        creds = ZjmfFinanceCredentials(
            base_url=base_url,
            username=data.username,
            password=data.password,
            account_type=account_type,
        )
        ok, message, latency = await test_zjmf_finance_connection(creds)
    else:
        creds = DcimCloudCredentials(
            base_url=base_url,
            username=data.username,
            password=data.password,
            account_type=account_type,
        )
        ok, message, latency = await test_connection(creds)
    return CloudServerTestResult(ok=ok, message=message, latency_ms=latency)
