"""Nexus — Platform & Node API Routes (Asset organization layer)
All operations require JWT authentication.
"""

from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query, Request

from server.api.dependencies import get_db
from server.api.auth_jwt import get_current_admin
from server.api.schemas import PlatformCreate, PlatformUpdate, NodeCreate, NodeUpdate
from server.infrastructure.database.platform_node_repo import PlatformRepositoryImpl, NodeRepositoryImpl
from server.infrastructure.database.audit_log_repo import AuditLogRepositoryImpl
from server.domain.models import Platform, Node, Admin, AuditLog

from sqlalchemy.ext.asyncio import AsyncSession

router = APIRouter(prefix="/api/assets", tags=["assets"])


# ── Platforms ──

@router.get("/platforms", response_model=list)
async def list_platforms(
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    """List all platform templates"""
    repo = PlatformRepositoryImpl(db)
    platforms = await repo.get_all()
    return [_platform_to_dict(p) for p in platforms]


@router.get("/platforms/{id}", response_model=dict)
async def get_platform(
    id: int,
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    """Get a single platform by ID"""
    repo = PlatformRepositoryImpl(db)
    platform = await repo.get_by_id(id)
    if not platform:
        raise HTTPException(status_code=404, detail="Platform not found")
    return _platform_to_dict(platform)


@router.post("/platforms", response_model=dict, status_code=201)
async def create_platform(
    payload: PlatformCreate,
    request: Request,
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    """Create a new platform template"""
    repo = PlatformRepositoryImpl(db)
    platform = Platform(**payload.model_dump(exclude_none=True))
    created = await repo.create(platform)

    audit_repo = AuditLogRepositoryImpl(db)
    await audit_repo.create(AuditLog(
        admin_username=admin.username, action="create_platform",
        target_type="platform", target_id=created.id,
        detail=f"名称={created.name}", ip_address=request.client.host if request.client else "",
    ))

    return _platform_to_dict(created)


@router.put("/platforms/{id}", response_model=dict)
async def update_platform(
    id: int,
    payload: PlatformUpdate,
    request: Request,
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    """Update a platform template"""
    repo = PlatformRepositoryImpl(db)
    platform = await repo.get_by_id(id)
    if not platform:
        raise HTTPException(status_code=404, detail="Platform not found")
    # Explicit allowlist to prevent mass-assignment to sensitive columns
    ALLOWED_FIELDS = {"name", "category", "type", "default_protocols", "charset"}
    for key, value in payload.model_dump(exclude_unset=True).items():
        if key in ALLOWED_FIELDS:
            setattr(platform, key, value)
    updated = await repo.update(platform)

    audit_repo = AuditLogRepositoryImpl(db)
    await audit_repo.create(AuditLog(
        admin_username=admin.username, action="update_platform",
        target_type="platform", target_id=id,
        detail=f"名称={updated.name}", ip_address=request.client.host if request.client else "",
    ))

    return _platform_to_dict(updated)


@router.delete("/platforms/{id}", status_code=204)
async def delete_platform(
    id: int,
    request: Request,
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    """Delete a platform template"""
    repo = PlatformRepositoryImpl(db)
    platform = await repo.get_by_id(id)
    if not platform:
        raise HTTPException(status_code=404, detail="Platform not found")
    result = await repo.delete(id)
    if not result:
        raise HTTPException(status_code=404, detail="Platform not found")

    audit_repo = AuditLogRepositoryImpl(db)
    await audit_repo.create(AuditLog(
        admin_username=admin.username, action="delete_platform",
        target_type="platform", target_id=id,
        detail=f"名称={platform.name}", ip_address=request.client.host if request.client else "",
    ))


# ── Nodes (Tree) ──

@router.get("/nodes", response_model=list)
async def list_nodes(
    parent_id: Optional[int] = Query(None, description="Filter by parent node ID"),
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    """List nodes — all, or children of a specific parent"""
    repo = NodeRepositoryImpl(db)
    if parent_id is not None:
        nodes = await repo.get_children(parent_id)
    else:
        nodes = await repo.get_all()
    return [_node_to_dict(n) for n in nodes]


@router.get("/nodes/tree", response_model=list)
async def get_node_tree(
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    """Get full node tree structure"""
    repo = NodeRepositoryImpl(db)
    all_nodes = await repo.get_all()
    return _build_tree(all_nodes)


@router.post("/nodes", response_model=dict, status_code=201)
async def create_node(
    payload: NodeCreate,
    request: Request,
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    """Create a new node"""
    repo = NodeRepositoryImpl(db)
    node = Node(**payload.model_dump(exclude_none=True))
    created = await repo.create(node)

    audit_repo = AuditLogRepositoryImpl(db)
    await audit_repo.create(AuditLog(
        admin_username=admin.username, action="create_node",
        target_type="node", target_id=created.id,
        detail=f"名称={created.name}", ip_address=request.client.host if request.client else "",
    ))

    return _node_to_dict(created)


@router.put("/nodes/{id}", response_model=dict)
async def update_node(
    id: int,
    payload: NodeUpdate,
    request: Request,
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    """Update a node"""
    repo = NodeRepositoryImpl(db)
    node = await repo.get_by_id(id)
    if not node:
        raise HTTPException(status_code=404, detail="Node not found")
    # Explicit allowlist to prevent mass-assignment to sensitive columns
    ALLOWED_FIELDS = {"name", "parent_id", "sort_order"}
    for key, value in payload.model_dump(exclude_unset=True).items():
        if key in ALLOWED_FIELDS:
            setattr(node, key, value)
    updated = await repo.update(node)

    audit_repo = AuditLogRepositoryImpl(db)
    await audit_repo.create(AuditLog(
        admin_username=admin.username, action="update_node",
        target_type="node", target_id=id,
        detail=f"名称={updated.name}", ip_address=request.client.host if request.client else "",
    ))

    return _node_to_dict(updated)


@router.delete("/nodes/{id}", status_code=204)
async def delete_node(
    id: int,
    request: Request,
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    """Delete a node"""
    repo = NodeRepositoryImpl(db)
    node = await repo.get_by_id(id)
    if not node:
        raise HTTPException(status_code=404, detail="Node not found")
    result = await repo.delete(id)
    if not result:
        raise HTTPException(status_code=404, detail="Node not found")

    audit_repo = AuditLogRepositoryImpl(db)
    await audit_repo.create(AuditLog(
        admin_username=admin.username, action="delete_node",
        target_type="node", target_id=id,
        detail=f"名称={node.name}", ip_address=request.client.host if request.client else "",
    ))


# ── SSH Sessions & Command Logs ──

@router.get("/ssh-sessions", response_model=list)
async def list_ssh_sessions(
    server_id: Optional[int] = Query(None, description="Filter by server ID"),
    limit: int = Query(50, ge=1, le=200),
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    """List SSH sessions with server name"""
    from server.infrastructure.database.ssh_session_repo import SshSessionRepositoryImpl
    from sqlalchemy import select
    from server.domain.models import SshSession, Server

    repo = SshSessionRepositoryImpl(db)
    if server_id:
        sessions = await repo.get_by_server(server_id, limit)
    else:
        result = await db.execute(select(SshSession).order_by(SshSession.started_at.desc()).limit(limit))
        sessions = list(result.scalars().all())

    # Batch load server names
    server_ids = {s.server_id for s in sessions if s.server_id}
    server_names = {}
    if server_ids:
        srv_result = await db.execute(select(Server.id, Server.name).where(Server.id.in_(server_ids)))
        server_names = dict(srv_result.all())

    return [_ssh_session_to_dict(s, server_names.get(s.server_id)) for s in sessions]


@router.get("/command-logs", response_model=list)
async def list_command_logs(
    server_id: Optional[int] = Query(None, description="Filter by server ID"),
    session_id: Optional[str] = Query(None, description="Filter by SSH session ID"),
    limit: int = Query(200, ge=1, le=500),
    admin: Admin = Depends(get_current_admin),
    db: AsyncSession = Depends(get_db),
):
    """List command logs with server name and admin username"""
    from server.infrastructure.database.ssh_session_repo import CommandLogRepositoryImpl
    from sqlalchemy import select
    from server.domain.models import CommandLog, Server, Admin as AdminModel

    repo = CommandLogRepositoryImpl(db)
    if session_id:
        logs = await repo.get_by_session(session_id, limit)
    elif server_id:
        logs = await repo.get_by_server(server_id, limit)
    else:
        result = await db.execute(select(CommandLog).order_by(CommandLog.created_at.desc()).limit(limit))
        logs = list(result.scalars().all())

    # Batch load server names and admin usernames
    server_ids = {l.server_id for l in logs if l.server_id}
    admin_ids = {l.admin_id for l in logs if l.admin_id}

    server_names = {}
    if server_ids:
        srv_result = await db.execute(select(Server.id, Server.name).where(Server.id.in_(server_ids)))
        server_names = dict(srv_result.all())

    admin_names = {}
    if admin_ids:
        adm_result = await db.execute(select(AdminModel.id, AdminModel.username).where(AdminModel.id.in_(admin_ids)))
        admin_names = dict(adm_result.all())

    return [_command_log_to_dict(l, server_names.get(l.server_id), admin_names.get(l.admin_id)) for l in logs]


# ── Helper functions ──

def _platform_to_dict(platform: Platform) -> dict:
    return {
        "id": platform.id,
        "name": platform.name,
        "category": platform.category,
        "type": platform.type,
        "default_protocols": platform.default_protocols,
        "charset": platform.charset,
        "created_at": str(platform.created_at) if platform.created_at else None,
    }


def _node_to_dict(node: Node) -> dict:
    return {
        "id": node.id,
        "name": node.name,
        "parent_id": node.parent_id,
        "sort_order": node.sort_order,
        "created_at": str(node.created_at) if node.created_at else None,
    }


def _build_tree(nodes: list) -> list:
    """Build tree structure from flat node list"""
    node_map = {n.id: {**_node_to_dict(n), "children": []} for n in nodes}
    tree = []
    for n in nodes:
        node_dict = node_map[n.id]
        if n.parent_id and n.parent_id in node_map:
            node_map[n.parent_id]["children"].append(node_dict)
        else:
            tree.append(node_dict)
    return tree


def _ssh_session_to_dict(session, server_name: str = None) -> dict:
    return {
        "id": session.id,
        "server_id": session.server_id,
        "server_name": server_name,
        "admin_id": session.admin_id,
        "remote_addr": session.remote_addr,
        "status": session.status,
        "started_at": str(session.started_at) if session.started_at else None,
        "closed_at": str(session.closed_at) if session.closed_at else None,
    }


def _command_log_to_dict(log, server_name: str = None, admin_username: str = None) -> dict:
    return {
        "id": log.id,
        "session_id": log.session_id,
        "server_id": log.server_id,
        "server_name": server_name,
        "admin_id": log.admin_id,
        "admin_username": admin_username,
        "command": log.command,
        "remote_addr": log.remote_addr,
        "created_at": str(log.created_at) if log.created_at else None,
    }