"""
Router pour l'onglet Ports & Services
Scan de services réseau sur les IPs connues — parallélisé par batch
"""
import asyncio
from datetime import datetime
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from pydantic import BaseModel
from typing import List, Dict, Any

from backend.app.core.database import get_db
from backend.app.core.config import config_manager
from backend.app.models.ip import IP
from backend.app.services.network import NetworkScanner
from backend.app.services.websocket import ws_manager

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

# Verrou pour empêcher les scans simultanés
_scan_lock = asyncio.Lock()

# Nombre d'IPs scannées en parallèle
PARALLEL_IPS = 20


class ServiceScanRequest(BaseModel):
    """Requête de scan de services"""
    ports: List[int]


@router.get("/list")
async def list_services():
    """Retourne la liste des services configurés dans config.yaml"""
    config = config_manager.config
    return {
        "services": [s.model_dump() for s in config.services]
    }


async def _scan_single_ip(
    scanner: NetworkScanner,
    ip_info: Dict[str, str],
    selected_ports: List[int],
    port_to_service: Dict[int, Dict[str, Any]],
    idx: int,
    total_ips: int,
    semaphore: asyncio.Semaphore
) -> list:
    """Scanne une IP avec sémaphore de concurrence"""
    async with semaphore:
        ip_addr = ip_info["ip"]

        try:
            open_ports = await scanner.scan_ports(ip_addr, selected_ports)
        except Exception as e:
            print(f"[Services] Erreur scan {ip_addr}: {e}")
            await ws_manager.broadcast({
                "type": "service_scan_log",
                "message": f"{ip_addr} — Erreur: {e}"
            })
            return []

        # Log du résultat
        if open_ports:
            port_names = []
            for p in open_ports:
                svc = port_to_service.get(p, {"name": str(p)})
                port_names.append(f"{svc['name']}({p})")
            await ws_manager.broadcast({
                "type": "service_scan_log",
                "message": f"{ip_addr} — {', '.join(port_names)}"
            })
        else:
            await ws_manager.broadcast({
                "type": "service_scan_log",
                "message": f"{ip_addr} — aucun service"
            })

        # Construire les résultats et les envoyer en temps réel
        ip_results = []
        for port in open_ports:
            svc_info = port_to_service.get(port, {"name": f"Port {port}", "protocol": None})
            protocol = svc_info["protocol"]

            url = None
            if protocol in ("http", "https"):
                if (protocol == "http" and port == 80) or (protocol == "https" and port == 443):
                    url = f"{protocol}://{ip_addr}"
                else:
                    url = f"{protocol}://{ip_addr}:{port}"

            result_entry = {
                "ip": ip_addr,
                "name": ip_info["name"],
                "hostname": ip_info["hostname"],
                "port": port,
                "service_name": svc_info["name"],
                "protocol": protocol or "",
                "url": url
            }
            ip_results.append(result_entry)

            # Envoyer le résultat en temps réel
            await ws_manager.broadcast({
                "type": "service_scan_result",
                "result": result_entry
            })

        return ip_results


async def _run_service_scan(selected_ports: List[int], ip_list: list):
    """Tâche de fond : scanne les IPs en parallèle et envoie les résultats via WebSocket"""
    config = config_manager.config

    # Mapping port → service
    port_to_service = {}
    for svc in config.services:
        port_to_service[svc.port] = {
            "name": svc.name,
            "protocol": svc.protocol
        }

    total_ips = len(ip_list)

    scanner = NetworkScanner(
        cidr=config.network.cidr,
        timeout=config.scan.timeout,
        ping_count=config.scan.ping_count
    )

    await ws_manager.broadcast({
        "type": "service_scan_start",
        "message": f"Scan démarré ({len(selected_ports)} ports, {total_ips} IPs, {PARALLEL_IPS} en parallèle)"
    })

    # Sémaphore pour limiter la concurrence
    semaphore = asyncio.Semaphore(PARALLEL_IPS)
    completed = 0

    async def scan_with_progress(ip_info, idx):
        nonlocal completed
        result = await _scan_single_ip(
            scanner, ip_info, selected_ports, port_to_service,
            idx, total_ips, semaphore
        )
        completed += 1
        # Progression
        await ws_manager.broadcast({
            "type": "service_scan_progress",
            "current": completed,
            "total": total_ips,
            "ip": ip_info["ip"]
        })
        return result

    # Lancer toutes les IPs en parallèle (limité par sémaphore)
    tasks = [
        scan_with_progress(ip_info, idx)
        for idx, ip_info in enumerate(ip_list)
    ]
    all_results = await asyncio.gather(*tasks)

    # Aplatir les résultats
    results = []
    for ip_results in all_results:
        results.extend(ip_results)

    # Trier par IP puis par port
    results.sort(key=lambda r: (
        tuple(int(p) for p in r["ip"].split(".")),
        r["port"]
    ))

    stats = {
        "total_ips": total_ips,
        "total_found": len(results),
        "scan_time": datetime.now().isoformat()
    }

    await ws_manager.broadcast({
        "type": "service_scan_complete",
        "message": f"Scan terminé : {len(results)} services détectés sur {total_ips} IPs",
        "stats": stats,
        "results": results
    })


@router.post("/scan")
async def scan_services(request: ServiceScanRequest, db: Session = Depends(get_db)):
    """
    Lance un scan de services en tâche de fond (parallélisé).
    Les résultats sont envoyés via WebSocket.
    """
    selected_ports = request.ports

    if not selected_ports:
        return {"status": "error", "message": "Aucun port sélectionné"}

    if _scan_lock.locked():
        return {"status": "error", "message": "Un scan de services est déjà en cours"}

    # Récupérer la liste des IPs
    all_ips = db.query(IP).filter(IP.last_seen.isnot(None)).all()
    ip_list = [
        {"ip": ip.ip, "name": ip.name or "", "hostname": ip.hostname or ""}
        for ip in all_ips
    ]

    if not ip_list:
        return {"status": "error", "message": "Aucune IP connue en base"}

    async def locked_scan():
        async with _scan_lock:
            await _run_service_scan(selected_ports, ip_list)

    asyncio.create_task(locked_scan())

    return {
        "status": "started",
        "message": f"Scan lancé pour {len(selected_ports)} ports sur {len(ip_list)} IPs ({PARALLEL_IPS} en parallèle)",
        "total_ips": len(ip_list)
    }
