"""
Scan Jobs Management Router
"""
from typing import List, Optional
from datetime import datetime, timedelta
from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.orm import Session
from sqlalchemy import func
from pydantic import BaseModel

from app.database import get_db
from app.models.scan import Scan, ScanType, ScanStatus
from app.models.scan_schedule import ScanSchedule, ScheduleInterval
from app.models.asset import Asset
from app.models.user import User
from app.auth.dependencies import get_current_user, RequireEditor
from app.routers.vulnerabilities import sync_agent_vulnerabilities

router = APIRouter(prefix="/api/v1/scans", tags=["Scans"])

# --- Schemas ---
class ScanResponse(BaseModel):
    id: int
    asset_id: int
    scan_type: ScanType
    status: ScanStatus
    started_at: Optional[datetime]
    completed_at: Optional[datetime]
    vulnerabilities_found: int
    error_message: Optional[str]
    asset_hostname: Optional[str] = None # For display convenience

    class Config:
        from_attributes = True

class ScheduleCreateRequest(BaseModel):
    name: str
    interval: ScheduleInterval
    cron_expression: Optional[str] = None
    scanner_type: str = "wazuh"  # "wazuh" | "nessus"

class ScheduleResponse(BaseModel):
    id: int
    name: str
    interval: ScheduleInterval
    cron_expression: Optional[str]
    scanner_type: str = "wazuh"
    enabled: bool
    last_run: Optional[datetime]
    next_run: Optional[datetime]
    created_by: Optional[int]

    class Config:
        from_attributes = True

# --- Endpoints ---

@router.get("", response_model=List[ScanResponse])
async def list_scans(
    status: Optional[ScanStatus] = Query(None),
    asset_id: Optional[int] = Query(None),
    limit: int = Query(50),
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    """List scans with optional filtering"""
    query = db.query(Scan)
    
    if status:
        query = query.filter(Scan.status == status)
    if asset_id:
        query = query.filter(Scan.asset_id == asset_id)
        
    # Order by newest first
    scans = query.order_by(Scan.created_at.desc()).limit(limit).all()

    # Enrich with asset hostname for UI
    results = []
    for s in scans:
        asset_name = s.asset.hostname if s.asset else "Unknown"
        row = {**s.__dict__, "asset_hostname": asset_name}
        row["scan_type"] = _display_scan_type(s.scan_type)
        results.append(row)

    return results


def _display_scan_type(scan_type) -> str:
    """
    Collapse legacy FULL / SYSCOLLECTOR enum values to the unified
    'wazuh' label for display. The DB still carries the original
    enum values (migration 020 is a no-op — Postgres rejected the
    in-place backfill, see that migration's docstring). MANUAL is
    a leftover from a removed feature; treat it as wazuh too since
    it was the only writer.
    """
    if scan_type is None:
        return "unknown"
    raw = scan_type.value if hasattr(scan_type, "value") else str(scan_type)
    lower = raw.lower()
    if lower in ("full", "syscollector", "manual"):
        return "wazuh"
    return lower

class ScanRunSummary(BaseModel):
    scan_ids: List[int]  # IDs of all scans in this run
    started_at: datetime
    completed_at: Optional[datetime]
    # String not enum — legacy FULL/SYSCOLLECTOR rows get collapsed to
    # 'wazuh' before serialization, so this carries the display label.
    scan_type: str
    total_assets: int
    completed: int
    failed: int
    running: int
    total_vulns_found: int
    failed_assets: List[dict]  # [{asset_hostname, error_message}]

    class Config:
        from_attributes = True


@router.get("/summary", response_model=List[ScanRunSummary])
async def list_scan_summaries(
    limit: int = Query(20),
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    """
    Returns scan runs grouped by time window (scans triggered within 5 seconds = one run).
    Shows summary per run instead of individual per-asset rows.
    """
    # Get all scans ordered by start time
    scans = db.query(Scan).order_by(Scan.started_at.desc()).limit(500).all()

    if not scans:
        return []

    # Group scans into "runs" — scans started within 5 seconds of each other
    runs = []
    current_run = [scans[0]]

    for scan in scans[1:]:
        time_diff = abs((current_run[0].started_at - scan.started_at).total_seconds())
        if time_diff <= 5 and scan.scan_type == current_run[0].scan_type:
            current_run.append(scan)
        else:
            runs.append(current_run)
            current_run = [scan]
    runs.append(current_run)

    # Build summaries
    summaries = []
    for run in runs[:limit]:
        failed_assets = []
        completed_count = 0
        failed_count = 0
        running_count = 0
        total_vulns = 0

        for s in run:
            if s.status == ScanStatus.COMPLETED:
                completed_count += 1
                total_vulns += s.vulnerabilities_found or 0
            elif s.status == ScanStatus.FAILED:
                failed_count += 1
                hostname = s.asset.hostname if s.asset else f"Asset #{s.asset_id}"
                failed_assets.append({
                    "asset_hostname": hostname,
                    "error_message": s.error_message or "Unknown error"
                })
            elif s.status == ScanStatus.RUNNING:
                running_count += 1

        completed_ats = [s.completed_at for s in run if s.completed_at]

        summaries.append(ScanRunSummary(
            scan_ids=[s.id for s in run],
            started_at=min(s.started_at for s in run),
            completed_at=max(completed_ats) if completed_ats else None,
            scan_type=_display_scan_type(run[0].scan_type),
            total_assets=len(run),
            completed=completed_count,
            failed=failed_count,
            running=running_count,
            total_vulns_found=total_vulns,
            failed_assets=failed_assets,
        ))

    return summaries


class ScanDeleteRequest(BaseModel):
    scan_ids: Optional[List[int]] = None  # Delete specific scans by ID
    status_filter: Optional[str] = None   # Delete by status: "failed", "completed", etc.
    all: bool = False                     # Delete all scans


@router.post("/clear", response_model=dict)
async def clear_scans(
    body: ScanDeleteRequest,
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireEditor)
):
    """Delete scan history by IDs, status, or all."""
    query = db.query(Scan)

    if body.scan_ids:
        query = query.filter(Scan.id.in_(body.scan_ids))
    elif body.status_filter:
        try:
            status_enum = ScanStatus(body.status_filter.lower())
        except ValueError:
            raise HTTPException(status_code=400, detail=f"Invalid status: {body.status_filter}")
        query = query.filter(Scan.status == status_enum)
    elif not body.all:
        raise HTTPException(status_code=400, detail="Provide scan_ids, status_filter, or set all=true")

    count = query.delete(synchronize_session=False)
    db.commit()
    return {"deleted": count}


# --- Autoscan Endpoint ---
import json
from app.integrations.wazuh_client import WazuhClient, WazuhAPIError
from app.models.setting import Setting

@router.post("/autoscan", response_model=dict)
async def trigger_autoscan(
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireEditor)
):
    """
    Trigger automated scans for ALL assets connected to Wazuh.
    """
    # 1. Get Wazuh Configuration from DB (transparently decrypted)
    from app.auth.setting_crypto import read_setting_value
    raw_wazuh = read_setting_value(db, "wazuh_config")
    if not raw_wazuh:
         raise HTTPException(status_code=400, detail="Wazuh configuration not found. Please configure settings first.")

    try:
        config = json.loads(raw_wazuh)
        api_url = config.get("api_url")
        username = config.get("username")
        password = config.get("password")
    except json.JSONDecodeError:
         raise HTTPException(status_code=500, detail="Invalid Wazuh configuration format.")

    if not all([api_url, username, password]):
         raise HTTPException(status_code=400, detail="Incomplete Wazuh configuration.")

    indexer_url = config.get("indexer_url")
    indexer_username = config.get("indexer_username")
    indexer_password = config.get("indexer_password")

    # 2. Find eligible assets
    assets = db.query(Asset).filter(Asset.wazuh_agent_id.isnot(None)).all()
    if not assets:
         return {"message": "No assets found with linked Wazuh Agent IDs.", "scans_triggered": 0}

    # 3. Initialize Wazuh Client
    triggered_count = 0
    errors = []

    try:
        with WazuhClient(
            base_url=api_url, 
            username=username, 
            password=password,
            indexer_url=indexer_url,
            indexer_username=indexer_username,
            indexer_password=indexer_password,
            verify_ssl=config.get("verify_ssl", False)
        ) as client:
            for asset in assets:
                try:
                    # Create Scan Record
                    scan = Scan(
                        asset_id=asset.id,
                        scan_type=ScanType.WAZUH,  # source-labelled (was SYSCOLLECTOR)
                        status=ScanStatus.RUNNING,
                        started_at=datetime.now()
                    )
                    db.add(scan)

                    # Sync vulnerabilities from Wazuh
                    sync_agent_vulnerabilities(db, client, asset.wazuh_agent_id, asset)

                    scan.status = ScanStatus.COMPLETED
                    scan.completed_at = datetime.now()
                    triggered_count += 1

                except Exception as e:
                    scan.status = ScanStatus.FAILED
                    scan.completed_at = datetime.now()
                    scan.error_message = str(e)
                    errors.append(f"{asset.hostname}: {str(e)}")
            
            db.commit()

    except Exception as e:
        # Mark all pending scans as FAILED since Wazuh connection failed
        for scan_obj in db.new:
            if isinstance(scan_obj, Scan) and scan_obj.status == ScanStatus.RUNNING:
                scan_obj.status = ScanStatus.FAILED
                scan_obj.error_message = f"Wazuh connection failed: {str(e)}"
                scan_obj.completed_at = datetime.now()
        db.commit()
        raise HTTPException(status_code=500, detail="Failed to connect to Wazuh. Check server logs for details.")

    return {
        "message": f"Triggered scans for {triggered_count} assets.",
        "scans_triggered": triggered_count,
        "errors": errors
    }


# --- Schedule Endpoints ---

INTERVAL_DELTA_MAP = {
    ScheduleInterval.EVERY_HOUR: timedelta(hours=1),
    ScheduleInterval.EVERY_6_HOURS: timedelta(hours=6),
    ScheduleInterval.EVERY_12_HOURS: timedelta(hours=12),
    ScheduleInterval.DAILY: timedelta(days=1),
    ScheduleInterval.WEEKLY: timedelta(weeks=1),
}

@router.get("/schedules", response_model=List[ScheduleResponse])
async def list_schedules(
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    """List of all scan schedules"""
    schedules = db.query(ScanSchedule).order_by(ScanSchedule.created_at.desc()).all()
    return schedules


@router.post("/schedules", response_model=ScheduleResponse, status_code=status.HTTP_201_CREATED)
async def create_schedule(
    data: ScheduleCreateRequest,
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireEditor)
):
    """Creates a new automated scan schedule"""
    schedule = ScanSchedule(
        name=data.name,
        interval=data.interval,
        scanner_type=data.scanner_type or "wazuh",
        enabled=True,
        created_by=current_user.id,
        cron_expression=data.cron_expression,
        next_run=datetime.now() + INTERVAL_DELTA_MAP.get(data.interval, timedelta(days=1))
    )
    db.add(schedule)
    db.commit()
    db.refresh(schedule)
    return schedule


@router.put("/schedules/{schedule_id}", response_model=ScheduleResponse)
async def update_schedule(
    schedule_id: int,
    data: ScheduleCreateRequest,
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireEditor)
):
    """Updates an existing scan schedule"""
    schedule = db.query(ScanSchedule).filter(ScanSchedule.id == schedule_id).first()
    if not schedule:
        raise HTTPException(status_code=404, detail="Schedule not found")

    schedule.name = data.name
    schedule.interval = data.interval
    schedule.cron_expression = data.cron_expression
    schedule.scanner_type = data.scanner_type or "wazuh"
    
    # Recalculate next_run
    if schedule.enabled:
        if schedule.interval == ScheduleInterval.CRON and schedule.cron_expression:
             # Basic calc or reset to pick up by scheduler sync
             schedule.next_run = None 
        else:
            schedule.next_run = datetime.now() + INTERVAL_DELTA_MAP.get(data.interval, timedelta(days=1))
    
    db.commit()
    db.refresh(schedule)
    return schedule


@router.patch("/schedules/{schedule_id}", response_model=ScheduleResponse)
async def toggle_schedule(
    schedule_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireEditor)
):
    """Activates/Deactivates a scan schedule"""
    schedule = db.query(ScanSchedule).filter(ScanSchedule.id == schedule_id).first()
    if not schedule:
        raise HTTPException(status_code=404, detail="Schedule not found")

    schedule.enabled = not schedule.enabled
    if schedule.enabled:
        if schedule.interval == ScheduleInterval.CRON and schedule.cron_expression:
            schedule.next_run = None # Picked up by sync
        else:
            schedule.next_run = datetime.now() + INTERVAL_DELTA_MAP.get(schedule.interval, timedelta(days=1))
    else:
        schedule.next_run = None
    db.commit()
    db.refresh(schedule)
    return schedule


@router.delete("/schedules/{schedule_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_schedule(
    schedule_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireEditor)
):
    """Deletes a scan schedule"""
    schedule = db.query(ScanSchedule).filter(ScanSchedule.id == schedule_id).first()
    if not schedule:
        raise HTTPException(status_code=404, detail="Schedule not found")

    db.delete(schedule)
    db.commit()
