"""Detection Lifecycle Management router."""

import hashlib
from datetime import datetime, timedelta
from typing import Optional
from uuid import UUID

from fastapi import APIRouter, Depends, Query
from sqlalchemy import func
from sqlalchemy.orm import Session

from app.database import get_db
from app.dependencies.auth import get_current_user, require_any_role
from app.domain.exceptions import EntityNotFoundError
from app.models.detection_lifecycle import (
    DetectionAsset, DetectionTechniqueMapping, DetectionValidation,
    TechniqueConfidenceScore, InfrastructureChangeLog,
)
from app.schemas.detection_lifecycle_schema import (
    DetectionAssetCreate, DetectionAssetUpdate, DetectionAssetOut,
    DetectionValidationCreate, DetectionValidationOut,
    TechniqueConfidenceOut,
    InfrastructureChangeCreate, InfrastructureChangeOut,
)
from app.services import detection_asset_service, decay_engine_service, audit_service

router = APIRouter(prefix="/detection-lifecycle", tags=["detection-lifecycle"])


def _now() -> datetime:
    return datetime.utcnow()


# ── Detection Assets ─────────────────────────────────────────────────────────

@router.post("/assets", response_model=DetectionAssetOut, status_code=201)
def create_asset(body: DetectionAssetCreate, db: Session = Depends(get_db), user=Depends(get_current_user)):
    asset = detection_asset_service.create_detection_asset(db, body.model_dump(), user.id)
    return asset


@router.get("/assets", response_model=list[DetectionAssetOut])
def list_assets(
    platform: Optional[str] = None,
    asset_type: Optional[str] = None,
    health_status: Optional[str] = None,
    technique_id: Optional[UUID] = None,
    is_active: Optional[bool] = True,
    db: Session = Depends(get_db),
    user=Depends(get_current_user),
):
    return detection_asset_service.list_assets(db, platform=platform, asset_type=asset_type, health_status=health_status, technique_id=technique_id, is_active=is_active)


@router.get("/assets/{asset_id}", response_model=DetectionAssetOut)
def get_asset(asset_id: UUID, db: Session = Depends(get_db), user=Depends(get_current_user)):
    return detection_asset_service.get_asset_with_details(db, asset_id)


@router.patch("/assets/{asset_id}", response_model=DetectionAssetOut)
def update_asset(asset_id: UUID, body: DetectionAssetUpdate, db: Session = Depends(get_db), user=Depends(get_current_user)):
    return detection_asset_service.update_detection_asset(db, asset_id, body.model_dump(exclude_unset=True), user.id)


@router.delete("/assets/{asset_id}", status_code=204)
def delete_asset(asset_id: UUID, db: Session = Depends(get_db), user=Depends(require_any_role("red_lead", "blue_lead"))):
    asset = db.query(DetectionAsset).filter(DetectionAsset.id == asset_id).first()
    if not asset:
        raise EntityNotFoundError("DetectionAsset", str(asset_id))
    asset.is_active = False
    db.commit()


# ── Technique Mappings ───────────────────────────────────────────────────────

@router.post("/assets/{asset_id}/techniques/{technique_id}")
def map_technique(
    asset_id: UUID, technique_id: UUID,
    coverage_type: str = Query("detect"),
    confidence_level: str = Query("medium"),
    db: Session = Depends(get_db),
    user=Depends(get_current_user),
):
    # Validate asset exists
    asset = db.query(DetectionAsset).filter(DetectionAsset.id == asset_id).first()
    if not asset:
        raise EntityNotFoundError("DetectionAsset", str(asset_id))

    # Prevent duplicate mappings
    existing = db.query(DetectionTechniqueMapping).filter(
        DetectionTechniqueMapping.detection_asset_id == asset_id,
        DetectionTechniqueMapping.technique_id == technique_id,
    ).first()
    if existing:
        # Update coverage/confidence on existing mapping instead of duplicating
        existing.coverage_type = coverage_type
        existing.confidence_level = confidence_level
        db.commit()
        return {"message": "Technique mapping updated", "mapping_id": str(existing.id)}

    mapping = DetectionTechniqueMapping(
        detection_asset_id=asset_id, technique_id=technique_id,
        coverage_type=coverage_type, confidence_level=confidence_level,
    )
    db.add(mapping)
    db.commit()
    return {"message": "Technique mapped", "mapping_id": str(mapping.id)}


@router.get("/techniques/{technique_id}/detections")
def get_technique_detections(technique_id: UUID, db: Session = Depends(get_db), user=Depends(get_current_user)):
    return detection_asset_service.get_technique_detection_summary(db, technique_id)


# ── Validations ──────────────────────────────────────────────────────────────

@router.post("/validations", response_model=DetectionValidationOut, status_code=201)
def create_validation(body: DetectionValidationCreate, db: Session = Depends(get_db), user=Depends(get_current_user)):
    asset = db.query(DetectionAsset).filter(DetectionAsset.id == body.detection_asset_id).first()
    if not asset:
        raise EntityNotFoundError("DetectionAsset", str(body.detection_asset_id))

    now = _now()
    validation = DetectionValidation(
        detection_asset_id=body.detection_asset_id,
        technique_id=body.technique_id,
        test_id=body.test_id,
        validation_result=body.validation_result,
        validation_method=body.validation_method,
        notes=body.notes,
        evidence_ids=[str(e) for e in (body.evidence_ids or [])],
        validated_by=user.id,
        validated_at=now,
        expires_at=now + timedelta(days=body.validity_days),
        rule_hash_at_validation=asset.rule_hash,
        log_source_version_at_validation=asset.log_source_version,
        infrastructure_hash_at_validation=asset.infrastructure_hash,
    )
    data = f"{validation.detection_asset_id}:{validation.validated_by}:{validation.validation_result}:{validation.validated_at}"
    validation.integrity_hash = hashlib.sha256(data.encode()).hexdigest()

    db.add(validation)
    db.commit()
    db.refresh(validation)

    if body.technique_id:
        decay_engine_service.calculate_confidence_for_technique(db, body.technique_id)

    audit_service.log_action(db, user.id, "DETECTION_VALIDATED", "detection_validation", str(validation.id),
        details={"asset_id": str(body.detection_asset_id), "result": body.validation_result, "validity_days": body.validity_days})

    return validation


@router.get("/validations", response_model=list[DetectionValidationOut])
def list_validations(
    asset_id: Optional[UUID] = None,
    technique_id: Optional[UUID] = None,
    is_valid: Optional[bool] = None,
    db: Session = Depends(get_db),
    user=Depends(get_current_user),
):
    query = db.query(DetectionValidation)
    if asset_id:
        query = query.filter(DetectionValidation.detection_asset_id == asset_id)
    if technique_id:
        query = query.filter(DetectionValidation.technique_id == technique_id)
    if is_valid is not None:
        query = query.filter(DetectionValidation.is_valid == is_valid)
    return query.order_by(DetectionValidation.validated_at.desc()).all()


@router.post("/validations/{validation_id}/invalidate")
def invalidate_validation(
    validation_id: UUID,
    reason: str = Query(...),
    details: Optional[str] = None,
    db: Session = Depends(get_db),
    user=Depends(require_any_role("admin", "blue_lead")),
):
    validation = db.query(DetectionValidation).filter(DetectionValidation.id == validation_id).first()
    if not validation:
        raise EntityNotFoundError("DetectionValidation", str(validation_id))

    from app.models.detection_lifecycle import InvalidationReason
    try:
        reason_enum = InvalidationReason(reason)
    except ValueError:
        reason_enum = InvalidationReason.manual

    validation.is_valid = False
    validation.invalidated_at = _now()
    validation.invalidation_reason = reason_enum
    validation.invalidation_details = details
    validation.invalidated_by = user.id
    db.commit()
    return {"message": "Validation invalidated"}


# ── Confidence Scores ────────────────────────────────────────────────────────

@router.get("/confidence", response_model=list[TechniqueConfidenceOut])
def list_confidence_scores(
    confidence_level: Optional[str] = None,
    min_score: Optional[float] = None,
    max_score: Optional[float] = None,
    db: Session = Depends(get_db),
    user=Depends(get_current_user),
):
    query = db.query(TechniqueConfidenceScore)
    if confidence_level:
        query = query.filter(TechniqueConfidenceScore.confidence_level == confidence_level)
    if min_score is not None:
        query = query.filter(TechniqueConfidenceScore.confidence_score >= min_score)
    if max_score is not None:
        query = query.filter(TechniqueConfidenceScore.confidence_score <= max_score)
    return query.order_by(TechniqueConfidenceScore.confidence_score.asc()).all()


@router.get("/confidence/{technique_id}", response_model=TechniqueConfidenceOut)
def get_technique_confidence(
    technique_id: UUID,
    recalculate: bool = Query(False),
    db: Session = Depends(get_db),
    user=Depends(get_current_user),
):
    if recalculate:
        return decay_engine_service.calculate_confidence_for_technique(db, technique_id)
    score = db.query(TechniqueConfidenceScore).filter(TechniqueConfidenceScore.technique_id == technique_id).first()
    if not score:
        return decay_engine_service.calculate_confidence_for_technique(db, technique_id)
    return score


# ── Infrastructure Changes ───────────────────────────────────────────────────

@router.post("/infrastructure-changes", response_model=InfrastructureChangeOut, status_code=201)
def report_infrastructure_change(
    body: InfrastructureChangeCreate,
    db: Session = Depends(get_db),
    user=Depends(require_any_role("admin", "blue_lead")),
):
    change = InfrastructureChangeLog(
        change_type=body.change_type,
        description=body.description,
        affected_platforms=body.affected_platforms,
        affected_log_sources=body.affected_log_sources,
        change_date=body.change_date or _now(),
        auto_invalidate=body.auto_invalidate,
        reported_by=user.id,
    )
    db.add(change)
    db.commit()
    db.refresh(change)

    if change.auto_invalidate:
        decay_engine_service.process_infrastructure_change(db, change.id)
        db.refresh(change)

    audit_service.log_action(db, user.id, "INFRASTRUCTURE_CHANGE_REPORTED", "infrastructure_change", str(change.id),
        details={"type": body.change_type, "invalidated_count": change.invalidated_count})

    return change


@router.get("/infrastructure-changes", response_model=list[InfrastructureChangeOut])
def list_infrastructure_changes(
    days: int = Query(90, ge=1, le=730),
    db: Session = Depends(get_db),
    user=Depends(get_current_user),
):
    cutoff = _now() - timedelta(days=days)
    return db.query(InfrastructureChangeLog).filter(InfrastructureChangeLog.change_date >= cutoff).order_by(InfrastructureChangeLog.change_date.desc()).all()


# ── Decay Engine Control ─────────────────────────────────────────────────────

@router.post("/decay-engine/run")
def trigger_decay_engine(db: Session = Depends(get_db), user=Depends(require_any_role("admin"))):
    results = decay_engine_service.run_decay_engine(db)
    return {"message": "Decay engine completed", "results": results}


# ── Dashboard ────────────────────────────────────────────────────────────────

@router.get("/dashboard")
def lifecycle_dashboard(db: Session = Depends(get_db), user=Depends(get_current_user)):
    now = _now()

    health_dist = dict(
        db.query(DetectionAsset.health_status, func.count(DetectionAsset.id))
        .filter(DetectionAsset.is_active == True)
        .group_by(DetectionAsset.health_status)
        .all()
    )
    confidence_dist = dict(
        db.query(TechniqueConfidenceScore.confidence_level, func.count(TechniqueConfidenceScore.id))
        .group_by(TechniqueConfidenceScore.confidence_level)
        .all()
    )
    expiring_soon = db.query(func.count(DetectionValidation.id)).filter(
        DetectionValidation.is_valid == True,
        DetectionValidation.expires_at <= (now + timedelta(days=7)),
    ).scalar() or 0

    total_assets = db.query(func.count(DetectionAsset.id)).filter(DetectionAsset.is_active == True).scalar() or 0
    total_valid = db.query(func.count(DetectionValidation.id)).filter(DetectionValidation.is_valid == True).scalar() or 0
    recent_changes = db.query(func.count(InfrastructureChangeLog.id)).filter(
        InfrastructureChangeLog.change_date >= (now - timedelta(days=30))
    ).scalar() or 0

    return {
        "total_detection_assets": total_assets,
        "total_valid_validations": total_valid,
        "health_distribution": {k.value if hasattr(k, "value") else str(k): v for k, v in health_dist.items()},
        "confidence_distribution": {k.value if hasattr(k, "value") else str(k): v for k, v in confidence_dist.items()},
        "validations_expiring_7d": expiring_soon,
        "infrastructure_changes_30d": recent_changes,
    }
