# app/modules/monitoring/routes/api/admin_code_quality.py
"""
Code Quality API Endpoints
RESTful API for code quality validation and violation management
Supports multiple validator types: architecture, security, performance
"""

from datetime import datetime
from enum import Enum

from fastapi import APIRouter, BackgroundTasks, Depends, Query
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session

from app.api.deps import get_current_admin_api
from app.core.database import get_db
from app.modules.analytics.schemas import CodeQualityDashboardStatsResponse
from app.modules.dev_tools.models import ArchitectureScan
from app.modules.dev_tools.services.code_quality_service import (
    VALID_VALIDATOR_TYPES,
    code_quality_service,
)
from app.modules.monitoring.exceptions import (
    ScanNotFoundException,
    ViolationNotFoundException,
)
from app.modules.tenancy.schemas.auth import UserContext

admin_code_quality_router = APIRouter(prefix="/code-quality")


# Enums and Constants


class ValidatorType(str, Enum):
    """Supported validator types"""

    ARCHITECTURE = "architecture"
    SECURITY = "security"
    PERFORMANCE = "performance"


# Pydantic Models for API


class ScanResponse(BaseModel):
    """Response model for a scan"""

    id: int
    timestamp: str
    validator_type: str
    status: str
    started_at: str | None
    completed_at: str | None
    progress_message: str | None
    total_files: int
    total_violations: int
    errors: int
    warnings: int
    duration_seconds: float
    triggered_by: str | None
    git_commit_hash: str | None
    error_message: str | None = None

    class Config:
        from_attributes = True


class ScanRequest(BaseModel):
    """Request model for triggering scans"""

    validator_types: list[ValidatorType] = Field(
        default=[ValidatorType.ARCHITECTURE, ValidatorType.SECURITY, ValidatorType.PERFORMANCE],
        description="Validator types to run",
    )


class ScanJobResponse(BaseModel):
    """Response model for a queued scan job"""

    id: int
    validator_type: str
    status: str
    message: str


class MultiScanJobResponse(BaseModel):
    """Response model for multiple queued scans (background task pattern)"""

    scans: list[ScanJobResponse]
    message: str
    status_url: str


class MultiScanResponse(BaseModel):
    """Response model for completed scans (legacy sync pattern)"""

    scans: list[ScanResponse]
    total_violations: int
    total_errors: int
    total_warnings: int


class ViolationResponse(BaseModel):
    """Response model for a violation"""

    id: int
    scan_id: int
    validator_type: str
    rule_id: str
    rule_name: str
    severity: str
    file_path: str
    line_number: int
    message: str
    context: str | None
    suggestion: str | None
    status: str
    assigned_to: int | None
    resolved_at: str | None
    resolved_by: int | None
    resolution_note: str | None
    created_at: str

    class Config:
        from_attributes = True


class ViolationListResponse(BaseModel):
    """Response model for paginated violations list"""

    violations: list[ViolationResponse]
    total: int
    page: int
    page_size: int
    total_pages: int


class ViolationDetailResponse(ViolationResponse):
    """Response model for single violation with relationships"""

    assignments: list = []
    comments: list = []


class AssignViolationRequest(BaseModel):
    """Request model for assigning a violation"""

    user_id: int = Field(..., description="User ID to assign to")
    due_date: datetime | None = Field(None, description="Due date for resolution")
    priority: str = Field(
        "medium", description="Priority level (low, medium, high, critical)"
    )


class ResolveViolationRequest(BaseModel):
    """Request model for resolving a violation"""

    resolution_note: str = Field(..., description="Note about the resolution")


class IgnoreViolationRequest(BaseModel):
    """Request model for ignoring a violation"""

    reason: str = Field(..., description="Reason for ignoring")


class AddCommentRequest(BaseModel):
    """Request model for adding a comment"""

    comment: str = Field(..., min_length=1, description="Comment text")


# API Endpoints


def _scan_to_response(scan: ArchitectureScan) -> ScanResponse:
    """Convert ArchitectureScan to ScanResponse."""
    return ScanResponse(
        id=scan.id,
        timestamp=scan.timestamp.isoformat() if scan.timestamp else None,
        validator_type=scan.validator_type,
        status=scan.status,
        started_at=scan.started_at.isoformat() if scan.started_at else None,
        completed_at=scan.completed_at.isoformat() if scan.completed_at else None,
        progress_message=scan.progress_message,
        total_files=scan.total_files or 0,
        total_violations=scan.total_violations or 0,
        errors=scan.errors or 0,
        warnings=scan.warnings or 0,
        duration_seconds=scan.duration_seconds or 0.0,
        triggered_by=scan.triggered_by,
        git_commit_hash=scan.git_commit_hash,
        error_message=scan.error_message,
    )


@admin_code_quality_router.post("/scan", response_model=MultiScanJobResponse, status_code=202)
async def trigger_scan(
    request: ScanRequest = None,
    background_tasks: BackgroundTasks = None,
    db: Session = Depends(get_db),
    current_user: UserContext = Depends(get_current_admin_api),
):
    """
    Trigger code quality scan(s) as background tasks.

    By default runs all validators. Specify validator_types to run specific validators.
    Returns immediately with job IDs. Poll /scan/{scan_id}/status for progress.

    Scans run asynchronously - users can browse other pages while scans execute.
    """
    if request is None:
        request = ScanRequest()

    scan_jobs = []
    triggered_by = f"manual:{current_user.username}"

    # Import dispatcher for Celery support
    from app.tasks.dispatcher import task_dispatcher

    for vtype in request.validator_types:
        # Create scan record with pending status via service
        scan = code_quality_service.create_pending_scan(
            db, validator_type=vtype.value, triggered_by=triggered_by
        )

        # Dispatch via task dispatcher (supports Celery or BackgroundTasks)
        celery_task_id = task_dispatcher.dispatch_code_quality_scan(
            background_tasks=background_tasks,
            scan_id=scan.id,
        )

        # Store Celery task ID if using Celery
        if celery_task_id:
            scan.celery_task_id = celery_task_id

        scan_jobs.append(
            ScanJobResponse(
                id=scan.id,
                validator_type=vtype.value,
                status="pending",
                message=f"{vtype.value.capitalize()} scan queued",
            )
        )

    db.commit()

    validator_names = ", ".join(vtype.value for vtype in request.validator_types)
    return MultiScanJobResponse(
        scans=scan_jobs,
        message=f"Scans queued for: {validator_names}",
        status_url="/admin/code-quality/scans/running",
    )


@admin_code_quality_router.get("/scans/{scan_id}/status", response_model=ScanResponse)
async def get_scan_status(
    scan_id: int,
    db: Session = Depends(get_db),
    current_user: UserContext = Depends(get_current_admin_api),
):
    """
    Get status of a specific scan.

    Use this endpoint to poll for scan completion.
    """
    scan = code_quality_service.get_scan_by_id(db, scan_id)
    if not scan:
        raise ScanNotFoundException(scan_id)

    return _scan_to_response(scan)


@admin_code_quality_router.get("/scans/running", response_model=list[ScanResponse])
async def get_running_scans(
    db: Session = Depends(get_db),
    current_user: UserContext = Depends(get_current_admin_api),
):
    """
    Get all currently running scans.

    Returns scans with status 'pending' or 'running'.
    """
    scans = code_quality_service.get_running_scans(db)
    return [_scan_to_response(scan) for scan in scans]


@admin_code_quality_router.get("/scans", response_model=list[ScanResponse])
async def list_scans(
    limit: int = Query(30, ge=1, le=100, description="Number of scans to return"),
    validator_type: ValidatorType | None = Query(
        None, description="Filter by validator type"
    ),
    db: Session = Depends(get_db),
    current_user: UserContext = Depends(get_current_admin_api),
):
    """
    Get scan history

    Returns recent scans for trend analysis.
    Optionally filter by validator type.
    """
    scans = code_quality_service.get_scan_history(
        db, limit=limit, validator_type=validator_type.value if validator_type else None
    )

    return [_scan_to_response(scan) for scan in scans]


@admin_code_quality_router.get("/violations", response_model=ViolationListResponse)
async def list_violations(
    scan_id: int | None = Query(
        None, description="Filter by scan ID (defaults to latest)"
    ),
    validator_type: ValidatorType | None = Query(
        None, description="Filter by validator type"
    ),
    severity: str | None = Query(
        None, description="Filter by severity (error, warning, info)"
    ),
    status: str | None = Query(
        None, description="Filter by status (open, assigned, resolved, ignored)"
    ),
    rule_id: str | None = Query(None, description="Filter by rule ID"),
    file_path: str | None = Query(
        None, description="Filter by file path (partial match)"
    ),
    page: int = Query(1, ge=1, description="Page number"),
    page_size: int = Query(50, ge=1, le=200, description="Items per page"),
    db: Session = Depends(get_db),
    current_user: UserContext = Depends(get_current_admin_api),
):
    """
    Get violations with filtering and pagination

    Returns violations from latest scan(s) by default.
    Filter by validator_type to get violations from a specific validator.
    """
    offset = (page - 1) * page_size

    violations, total = code_quality_service.get_violations(
        db,
        scan_id=scan_id,
        validator_type=validator_type.value if validator_type else None,
        severity=severity,
        status=status,
        rule_id=rule_id,
        file_path=file_path,
        limit=page_size,
        offset=offset,
    )

    total_pages = (total + page_size - 1) // page_size

    return ViolationListResponse(
        violations=[
            ViolationResponse(
                id=v.id,
                scan_id=v.scan_id,
                validator_type=v.validator_type,
                rule_id=v.rule_id,
                rule_name=v.rule_name,
                severity=v.severity,
                file_path=v.file_path,
                line_number=v.line_number,
                message=v.message,
                context=v.context,
                suggestion=v.suggestion,
                status=v.status,
                assigned_to=v.assigned_to,
                resolved_at=v.resolved_at.isoformat() if v.resolved_at else None,
                resolved_by=v.resolved_by,
                resolution_note=v.resolution_note,
                created_at=v.created_at.isoformat(),
            )
            for v in violations
        ],
        total=total,
        page=page,
        page_size=page_size,
        total_pages=total_pages,
    )


@admin_code_quality_router.get("/violations/{violation_id}", response_model=ViolationDetailResponse)
async def get_violation(
    violation_id: int,
    db: Session = Depends(get_db),
    current_user: UserContext = Depends(get_current_admin_api),
):
    """
    Get single violation with details

    Includes assignments and comments.
    """
    violation = code_quality_service.get_violation_by_id(db, violation_id)

    if not violation:
        raise ViolationNotFoundException(violation_id)

    # Format assignments
    assignments = [
        {
            "id": a.id,
            "user_id": a.user_id,
            "assigned_at": a.assigned_at.isoformat(),
            "assigned_by": a.assigned_by,
            "due_date": a.due_date.isoformat() if a.due_date else None,
            "priority": a.priority,
        }
        for a in violation.assignments
    ]

    # Format comments
    comments = [
        {
            "id": c.id,
            "user_id": c.user_id,
            "comment": c.comment,
            "created_at": c.created_at.isoformat(),
        }
        for c in violation.comments
    ]

    return ViolationDetailResponse(
        id=violation.id,
        scan_id=violation.scan_id,
        validator_type=violation.validator_type,
        rule_id=violation.rule_id,
        rule_name=violation.rule_name,
        severity=violation.severity,
        file_path=violation.file_path,
        line_number=violation.line_number,
        message=violation.message,
        context=violation.context,
        suggestion=violation.suggestion,
        status=violation.status,
        assigned_to=violation.assigned_to,
        resolved_at=(
            violation.resolved_at.isoformat() if violation.resolved_at else None
        ),
        resolved_by=violation.resolved_by,
        resolution_note=violation.resolution_note,
        created_at=violation.created_at.isoformat(),
        assignments=assignments,
        comments=comments,
    )


@admin_code_quality_router.post("/violations/{violation_id}/assign")
async def assign_violation(
    violation_id: int,
    request: AssignViolationRequest,
    db: Session = Depends(get_db),
    current_user: UserContext = Depends(get_current_admin_api),
):
    """
    Assign violation to a developer

    Updates violation status to 'assigned'.
    """
    assignment = code_quality_service.assign_violation(
        db,
        violation_id=violation_id,
        user_id=request.user_id,
        assigned_by=current_user.id,
        due_date=request.due_date,
        priority=request.priority,
    )
    db.commit()

    return {
        "id": assignment.id,
        "violation_id": assignment.violation_id,
        "user_id": assignment.user_id,
        "assigned_at": assignment.assigned_at.isoformat(),
        "assigned_by": assignment.assigned_by,
        "due_date": (assignment.due_date.isoformat() if assignment.due_date else None),
        "priority": assignment.priority,
    }


@admin_code_quality_router.post("/violations/{violation_id}/resolve")
async def resolve_violation(
    violation_id: int,
    request: ResolveViolationRequest,
    db: Session = Depends(get_db),
    current_user: UserContext = Depends(get_current_admin_api),
):
    """
    Mark violation as resolved

    Records resolution timestamp and user.
    ViolationNotFoundException bubbles up if violation doesn't exist.
    """
    violation = code_quality_service.resolve_violation(
        db,
        violation_id=violation_id,
        resolved_by=current_user.id,
        resolution_note=request.resolution_note,
    )
    db.commit()

    return {
        "id": violation.id,
        "status": violation.status,
        "resolved_at": (
            violation.resolved_at.isoformat() if violation.resolved_at else None
        ),
        "resolved_by": violation.resolved_by,
        "resolution_note": violation.resolution_note,
    }


@admin_code_quality_router.post("/violations/{violation_id}/ignore")
async def ignore_violation(
    violation_id: int,
    request: IgnoreViolationRequest,
    db: Session = Depends(get_db),
    current_user: UserContext = Depends(get_current_admin_api),
):
    """
    Mark violation as ignored (won't fix)

    Records reason for ignoring.
    ViolationNotFoundException bubbles up if violation doesn't exist.
    """
    violation = code_quality_service.ignore_violation(
        db,
        violation_id=violation_id,
        ignored_by=current_user.id,
        reason=request.reason,
    )
    db.commit()

    return {
        "id": violation.id,
        "status": violation.status,
        "resolved_at": (
            violation.resolved_at.isoformat() if violation.resolved_at else None
        ),
        "resolved_by": violation.resolved_by,
        "resolution_note": violation.resolution_note,
    }


@admin_code_quality_router.post("/violations/{violation_id}/comments")
async def add_comment(
    violation_id: int,
    request: AddCommentRequest,
    db: Session = Depends(get_db),
    current_user: UserContext = Depends(get_current_admin_api),
):
    """
    Add comment to violation

    For team collaboration and discussion.
    """
    comment = code_quality_service.add_comment(
        db,
        violation_id=violation_id,
        user_id=current_user.id,
        comment=request.comment,
    )
    db.commit()

    return {
        "id": comment.id,
        "violation_id": comment.violation_id,
        "user_id": comment.user_id,
        "comment": comment.comment,
        "created_at": comment.created_at.isoformat(),
    }


@admin_code_quality_router.get("/stats", response_model=CodeQualityDashboardStatsResponse)
async def get_dashboard_stats(
    validator_type: ValidatorType | None = Query(
        None, description="Filter by validator type (returns combined stats if not specified)"
    ),
    db: Session = Depends(get_db),
    current_user: UserContext = Depends(get_current_admin_api),
):
    """
    Get dashboard statistics

    Returns comprehensive stats for the dashboard including:
    - Total counts by severity and status
    - Technical debt score
    - Trend data (last 7 scans)
    - Top violating files
    - Violations by rule and module
    - Per-validator breakdown

    When validator_type is specified, returns stats for that type only.
    When not specified, returns combined stats across all validators.
    """
    stats = code_quality_service.get_dashboard_stats(
        db, validator_type=validator_type.value if validator_type else None
    )

    return CodeQualityDashboardStatsResponse(**stats)


@admin_code_quality_router.get("/validator-types")
async def get_validator_types(
    current_user: UserContext = Depends(get_current_admin_api),
):
    """
    Get list of available validator types

    Returns the supported validator types for filtering.
    """
    return {
        "validator_types": VALID_VALIDATOR_TYPES,
        "descriptions": {
            "architecture": "Architectural patterns and code organization rules",
            "security": "Security vulnerabilities and best practices",
            "performance": "Performance issues and optimizations",
        },
    }
