"""
Endpoints API pour le contrôle des scans réseau
"""
from fastapi import APIRouter, Depends, BackgroundTasks
from sqlalchemy.orm import Session
from datetime import datetime, timedelta
from typing import Dict, Any, Optional, List
from pydantic import BaseModel

from backend.app.core.database import get_db
from backend.app.core.config import config_manager
from backend.app.models.ip import IP, IPHistory
from backend.app.models.scan_log import ScanLog
from backend.app.services.network import NetworkScanner, OuiLookup
from backend.app.services.websocket import ws_manager

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


class ScanLogResponse(BaseModel):
    """Schéma de réponse logs scan"""
    id: int
    ip: Optional[str]
    status: Optional[str]
    message: str
    created_at: datetime

    class Config:
        from_attributes = True


async def perform_scan(db: Session):
    """
    Effectue un scan complet du réseau
    Fonction asynchrone pour background task

    Args:
        db: Session de base de données
    """
    try:
        async def scan_log(message: str):
            print(message)
            try:
                await ws_manager.broadcast_scan_log(message)
            except Exception:
                pass

        await scan_log(f"[{datetime.now()}] Début du scan réseau...")

        # Notifier début du scan
        try:
            await ws_manager.broadcast_scan_start()
        except Exception as e:
            print(f"Erreur broadcast start (ignorée): {e}")

        # Récupérer la config
        config = config_manager.config
        await scan_log(f"[{datetime.now()}] Config chargée: {config.network.cidr}")

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

        # Convertir les ports en liste d'entiers
        port_list = []
        for port_range in config.ports.ranges:
            if '-' in port_range:
                start, end = map(int, port_range.split('-'))
                port_list.extend(range(start, end + 1))
            else:
                port_list.append(int(port_range))

        await scan_log(f"[{datetime.now()}] Ports à scanner: {len(port_list)}")

        # Récupérer les IPs connues
        known_ips = config.ip_classes
        await scan_log(f"[{datetime.now()}] IPs connues: {len(known_ips)}")

        # Callback de progression pour WebSocket
        async def progress_callback(current: int, total: int, current_ip: str, status: str, ping_ok: bool):
            try:
                ping_label = "ok" if ping_ok else "fail"
                await ws_manager.broadcast_scan_progress({
                    "current": current,
                    "total": total,
                    "ip": current_ip
                })
                await ws_manager.broadcast_scan_log(
                    f"[{current}/{total}] {current_ip} -> ping:{ping_label} ({status})"
                )
            except Exception:
                # Ignorer les erreurs WebSocket pour ne pas bloquer le scan
                pass

        # Lancer le scan
        await scan_log(f"[{datetime.now()}] Lancement du scan (parallélisme: {config.scan.parallel_pings})...")
        scan_results = await scanner.full_scan(
            known_ips=known_ips,
            port_list=port_list,
            max_concurrent=config.scan.parallel_pings,
            progress_callback=progress_callback
        )
        await scan_log(f"[{datetime.now()}] Scan terminé: {len(scan_results)} IPs trouvées")

        # Mettre à jour la base de données
        stats = {
            "total": 0,
            "online": 0,
            "offline": 0,
            "new": 0,
            "updated": 0
        }

        for ip_address, ip_data in scan_results.items():
            stats["total"] += 1

            if ip_data["last_status"] == "online":
                stats["online"] += 1
            else:
                stats["offline"] += 1

            # Log par IP (historique scan)
            ping_label = "ok" if ip_data["last_status"] == "online" else "fail"
            log_message = f"Scan {ip_address} -> ping:{ping_label} ({ip_data['last_status']})"
            db.add(ScanLog(
                ip=ip_address,
                status=ip_data["last_status"],
                message=log_message
            ))

            # Vérifier si l'IP existe déjà
            existing_ip = db.query(IP).filter(IP.ip == ip_address).first()

            if existing_ip:
                # Mettre à jour l'IP existante
                old_status = existing_ip.last_status

                # Si l'IP passe de offline à online ET qu'elle était inconnue, c'est une "nouvelle détection"
                # On réinitialise first_seen pour qu'elle apparaisse dans "Nouvelles Détections"
                if (old_status == "offline" and ip_data["last_status"] == "online" and not existing_ip.known):
                    existing_ip.first_seen = datetime.now()

                # Détecter changement de MAC address
                new_mac = ip_data.get("mac")
                if new_mac and existing_ip.mac and new_mac != existing_ip.mac:
                    # MAC a changé ! Marquer comme changée
                    existing_ip.mac_changed = True
                    print(f"[ALERTE] MAC changée pour {ip_address}: {existing_ip.mac} -> {new_mac}")
                else:
                    # Pas de changement ou pas de MAC précédente
                    existing_ip.mac_changed = False

                existing_ip.last_status = ip_data["last_status"]
                if ip_data["last_seen"]:
                    existing_ip.last_seen = ip_data["last_seen"]
                existing_ip.mac = ip_data.get("mac") or existing_ip.mac

                vendor = ip_data.get("vendor")
                if (not vendor or vendor == "Unknown") and existing_ip.mac:
                    vendor = OuiLookup.lookup(existing_ip.mac) or vendor
                if config.scan.force_vendor_update:
                    if vendor and vendor != "Unknown":
                        existing_ip.vendor = vendor
                else:
                    if (not existing_ip.vendor or existing_ip.vendor == "Unknown") and vendor and vendor != "Unknown":
                        existing_ip.vendor = vendor
                existing_ip.hostname = ip_data.get("hostname") or existing_ip.hostname
                existing_ip.open_ports = ip_data.get("open_ports", [])

                # Mettre à jour host seulement si présent dans ip_data (config)
                if "host" in ip_data:
                    existing_ip.host = ip_data["host"]

                # Mettre à jour le flag network_device (basé sur host="Network")
                # Utiliser le host existant si ip_data n'en a pas
                current_host = ip_data.get("host") or existing_ip.host
                existing_ip.network_device = (current_host == "Network")

                # Si l'état a changé, notifier via WebSocket
                if old_status != ip_data["last_status"]:
                    await ws_manager.broadcast_ip_update({
                        "ip": ip_address,
                        "old_status": old_status,
                        "new_status": ip_data["last_status"]
                    })

                stats["updated"] += 1

            else:
                # Créer une nouvelle IP
                vendor = ip_data.get("vendor")
                if (not vendor or vendor == "Unknown") and ip_data.get("mac"):
                    vendor = OuiLookup.lookup(ip_data.get("mac")) or vendor
                new_ip = IP(
                    ip=ip_address,
                    name=ip_data.get("name"),
                    known=ip_data.get("known", False),
                    network_device=ip_data.get("host") == "Network",
                    location=ip_data.get("location"),
                    host=ip_data.get("host"),
                    first_seen=datetime.now(),
                    last_seen=ip_data.get("last_seen") or datetime.now(),
                    last_status=ip_data["last_status"],
                    mac=ip_data.get("mac"),
                    vendor=vendor,
                    hostname=ip_data.get("hostname"),
                    open_ports=ip_data.get("open_ports", [])
                )
                db.add(new_ip)

                # Notifier nouvelle IP
                await ws_manager.broadcast_new_ip({
                    "ip": ip_address,
                    "status": ip_data["last_status"],
                    "known": ip_data.get("known", False)
                })

                stats["new"] += 1

            # Ajouter à l'historique
            history_entry = IPHistory(
                ip=ip_address,
                timestamp=datetime.now(),
                status=ip_data["last_status"],
                open_ports=ip_data.get("open_ports", [])
            )
            db.add(history_entry)

        # Commit les changements
        db.commit()

        # Notifier fin du scan avec stats
        await ws_manager.broadcast_scan_complete(stats)

        print(f"[{datetime.now()}] Scan terminé: {stats}")

    except Exception as e:
        print(f"Erreur lors du scan: {e}")
        db.rollback()


@router.post("/start")
async def start_scan(background_tasks: BackgroundTasks, db: Session = Depends(get_db)):
    """
    Déclenche un scan réseau immédiat

    Returns:
        Message de confirmation
    """
    # Lancer le scan en arrière-plan
    background_tasks.add_task(perform_scan, db)

    return {
        "message": "Scan réseau démarré",
        "timestamp": datetime.now()
    }


@router.get("/logs", response_model=List[ScanLogResponse])
async def get_scan_logs(limit: int = 200, db: Session = Depends(get_db)):
    """
    Retourne les derniers logs de scan
    """
    logs = db.query(ScanLog).order_by(ScanLog.created_at.desc()).limit(limit).all()
    return list(reversed(logs))


@router.post("/ports/{ip_address}")
async def scan_ip_ports(ip_address: str, db: Session = Depends(get_db)):
    """
    Scanne les ports d'une IP spécifique

    Args:
        ip_address: Adresse IP à scanner
        db: Session de base de données

    Returns:
        Liste des ports ouverts
    """
    try:
        # Récupérer la config
        config = config_manager.config

        # Convertir les ports en liste d'entiers
        port_list = []
        for port_range in config.ports.ranges:
            if '-' in port_range:
                start, end = map(int, port_range.split('-'))
                port_list.extend(range(start, end + 1))
            else:
                port_list.append(int(port_range))

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

        # Scanner les ports de cette IP
        print(f"[{datetime.now()}] Scan ports pour {ip_address}...")
        open_ports = await scanner.scan_ports(ip_address, port_list)
        print(f"[{datetime.now()}] Ports ouverts pour {ip_address}: {open_ports}")

        # Mettre à jour la base de données
        ip_record = db.query(IP).filter(IP.ip == ip_address).first()
        if ip_record:
            ip_record.open_ports = open_ports
            ip_record.last_seen = datetime.now()
            db.commit()

            # Notifier via WebSocket
            await ws_manager.broadcast_ip_update({
                "ip": ip_address,
                "open_ports": open_ports
            })

        return {
            "message": "Scan de ports terminé",
            "ip": ip_address,
            "open_ports": open_ports,
            "timestamp": datetime.now()
        }

    except Exception as e:
        print(f"Erreur scan ports {ip_address}: {e}")
        return {
            "message": f"Erreur: {str(e)}",
            "ip": ip_address,
            "open_ports": [],
            "timestamp": datetime.now()
        }


@router.post("/cleanup-history")
async def cleanup_history(hours: int = 24, db: Session = Depends(get_db)):
    """
    Nettoie l'historique plus ancien que X heures

    Args:
        hours: Nombre d'heures à conserver (défaut: 24h)
        db: Session de base de données

    Returns:
        Nombre d'entrées supprimées
    """
    cutoff_date = datetime.now() - timedelta(hours=hours)

    deleted = db.query(IPHistory).filter(
        IPHistory.timestamp < cutoff_date
    ).delete()

    db.commit()

    return {
        "message": f"Historique nettoyé",
        "deleted_entries": deleted,
        "older_than_hours": hours
    }
