"""
Nessus integration HTTP endpoints.

- POST /api/v1/vulnerabilities/sync/nessus     trigger sync
- GET  /api/v1/vulnerabilities/nessus/scans    list visible scans
- POST /api/v1/vulnerabilities/nessus/test     auth/connectivity probe

Config lives in settings table key `nessus_config` (JSON).
"""
from __future__ import annotations

import logging
from typing import List, Optional

from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session

from app.auth.dependencies import RequireAdmin, RequireEditor
from app.database import get_db
from app.integrations.nessus_client import (
    NessusAPIError,
    NessusAuthenticationError,
    NessusClient,
)
from app.models.asset import Asset
from app.models.user import User
from app.models.vulnerability import Vulnerability
from app.services.nessus_sync import load_nessus_config, run_nessus_sync

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/v1/vulnerabilities/nessus", tags=["Nessus"])


# ---------- schemas ----------
class NessusSyncRequest(BaseModel):
    scan_ids: Optional[List[int]] = Field(
        default=None,
        description="Optional explicit list of Nessus scan IDs. If omitted, uses nessus_config.default_scan_ids.",
    )


class NessusScanHostRequest(BaseModel):
    asset_id: int = Field(..., description="VulnCheck asset ID to rescan")
    scan_id: Optional[int] = Field(
        default=None,
        description=(
            "Nessus scan ID to launch. If omitted, uses the first ID from "
            "nessus_config.default_scan_ids."
        ),
    )


# ---------- endpoints ----------
@router.post("/test")
async def test_nessus_connection(
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireAdmin),
):
    """
    Verify Nessus API keys + URL by hitting /server/properties.
    Returns server version + plugin set + scan count when successful.
    """
    config = load_nessus_config(db)
    if not config:
        raise HTTPException(400, "nessus_config is not set in Settings")

    try:
        with NessusClient(
            base_url=config["base_url"],
            access_key=config["access_key"],
            secret_key=config["secret_key"],
            verify_ssl=bool(config.get("verify_ssl", True)),
        ) as client:
            ping = client.ping()
            scans = client.list_scans()
        return {
            "ok": True,
            "server_version": ping.get("server_version"),
            "feed": ping.get("feed"),
            "loaded_plugin_set": ping.get("loaded_plugin_set"),
            "scans_visible": len(scans),
        }
    except NessusAuthenticationError as e:
        return {"ok": False, "step": "auth", "error": str(e)}
    except NessusAPIError as e:
        return {"ok": False, "step": "api", "error": str(e)}
    except Exception as e:
        logger.warning("Nessus test failed: %s", e)
        return {"ok": False, "step": "transport", "error": str(e)}


@router.get("/scans")
async def list_nessus_scans(
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireEditor),
):
    """List scans visible to the configured Nessus API keys. Used by the
    Settings UI to populate the default_scan_ids picker."""
    config = load_nessus_config(db)
    if not config:
        raise HTTPException(400, "nessus_config is not set")
    try:
        with NessusClient(
            base_url=config["base_url"],
            access_key=config["access_key"],
            secret_key=config["secret_key"],
            verify_ssl=bool(config.get("verify_ssl", True)),
        ) as client:
            scans = client.list_scans()
        # Trim to fields the UI cares about
        return {
            "scans": [
                {
                    "id": s.get("id"),
                    "name": s.get("name"),
                    "status": s.get("status"),
                    "last_modification_date": s.get("last_modification_date"),
                    "host_count": s.get("host_count"),
                }
                for s in scans
            ]
        }
    except NessusAPIError as e:
        raise HTTPException(502, f"Nessus API error: {e}")


@router.post("/sync")
async def trigger_nessus_sync(
    payload: NessusSyncRequest,
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireEditor),
):
    """
    Manual Nessus sync trigger. Mirrors the Wazuh sync UX.

    Body shape: {"scan_ids":[12,17]}  — or empty to use defaults.
    """
    try:
        stats = run_nessus_sync(db, scan_ids=payload.scan_ids)
        return {"message": "Nessus sync completed", **stats}
    except RuntimeError as e:
        # config missing
        raise HTTPException(400, str(e))
    except NessusAuthenticationError as e:
        raise HTTPException(401, str(e))
    except NessusAPIError as e:
        raise HTTPException(502, f"Nessus API error: {e}")
    except Exception as e:
        logger.exception("Nessus sync crashed")
        raise HTTPException(500, f"Nessus sync failed: {e}")


@router.post("/scan-host")
async def nessus_scan_host(
    payload: NessusScanHostRequest,
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireEditor),
):
    """
    Launch a targeted Nessus scan for a single asset.

    Nessus ``POST /scans/{id}/launch`` supports ``alt_targets`` which overrides
    the scan's configured scope.  VulnCheck resolves the asset's IP and passes
    it as the only target — useful for post-patch rescans without waiting for
    the next full scheduled scan.

    Returns the Nessus ``scan_uuid`` so the caller can poll status if needed.
    """
    # 1. Resolve asset
    asset = db.query(Asset).filter(Asset.id == payload.asset_id).first()
    if not asset:
        raise HTTPException(404, f"Asset {payload.asset_id} not found")

    if not asset.ip_address:
        raise HTTPException(
            422,
            f"Asset '{asset.hostname}' has no IP address — Nessus cannot target it",
        )

    # 2. Load nessus config
    config = load_nessus_config(db)
    if not config:
        raise HTTPException(400, "Nessus is not configured (Settings → Tenable Nessus)")

    # 3. Pick scan_id and launch.
    # Order:  explicit payload.scan_id  →  nessus_config.default_scan_ids[0]
    #         →  fallback: first available scan from Nessus
    scan_id = payload.scan_id
    auto_picked = False
    try:
        with NessusClient(
            base_url=config["base_url"],
            access_key=config["access_key"],
            secret_key=config["secret_key"],
            verify_ssl=bool(config.get("verify_ssl", True)),
        ) as client:
            if not scan_id:
                defaults = config.get("default_scan_ids") or []
                if defaults:
                    scan_id = defaults[0]
                else:
                    # Auto-pick: take the first scan visible to the API keys.
                    # Saves the user from configuring default_scan_ids just to
                    # use the rescan button — most installs have a single
                    # "Basic Network Scan" template that fits.
                    available = client.list_scans()
                    if not available:
                        raise HTTPException(
                            400,
                            "Nessus has no visible scans for these API keys. "
                            "Create a scan template in Nessus first.",
                        )
                    scan_id = int(available[0]["id"])
                    auto_picked = True
                    logger.info(
                        "Nessus scan-host auto-picked scan_id=%s (%s) — "
                        "set default_scan_ids in Settings to pin this.",
                        scan_id, available[0].get("name", "?"),
                    )

            result = client.launch_scan(
                scan_id=scan_id,
                alt_targets=[asset.ip_address],
            )
    except HTTPException:
        # Let our own 4xx through untouched (e.g. "no scans visible").
        raise
    except NessusAuthenticationError as e:
        raise HTTPException(401, str(e))
    except NessusAPIError as e:
        raise HTTPException(502, f"Nessus API error: {e}")
    except Exception as e:
        logger.exception("Nessus scan-host launch failed")
        raise HTTPException(500, f"Failed to launch Nessus scan: {e}")

    logger.info(
        "Nessus targeted scan launched for asset %s (%s) via scan %s — uuid=%s",
        asset.hostname,
        asset.ip_address,
        scan_id,
        result.get("scan_uuid"),
    )
    suffix = (
        f"Auto-picked scan #{scan_id} (no default configured — set one in Settings to pin it). "
        if auto_picked
        else ""
    )
    return {
        "ok": True,
        "scan_uuid": result.get("scan_uuid"),
        "nessus_scan_id": scan_id,
        "auto_picked": auto_picked,
        "target_ip": asset.ip_address,
        "asset_hostname": asset.hostname,
        "message": (
            f"Nessus scan launched for {asset.hostname} ({asset.ip_address}). "
            f"{suffix}"
            "After it completes, use 'Sync Data (Nessus)' to import results."
        ),
    }


@router.post("/cleanup-pseudo-cves")
async def cleanup_pseudo_cves(
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireAdmin),
):
    """
    Delete vulnerabilities with pseudo-CVE ids (``NESSUS-PLUGIN-*``).

    These were imported by an earlier policy that mirrored non-CVE Nessus
    plugins (EOL / Compliance / Ciphers) into the Vulns list. The current
    sync skips those — this endpoint removes the leftovers.
    """
    q = db.query(Vulnerability).filter(Vulnerability.cve_id.like("NESSUS-PLUGIN-%"))
    count = q.count()
    if count == 0:
        return {"ok": True, "deleted": 0, "message": "No pseudo-CVEs found."}

    q.delete(synchronize_session=False)
    db.commit()
    logger.info("Pseudo-CVE cleanup: %d rows deleted by %s", count, current_user.username)
    return {
        "ok": True,
        "deleted": count,
        "message": f"Deleted {count} pseudo-CVE vulnerabilities (NESSUS-PLUGIN-*).",
    }
