# mypy: disable-error-code="arg-type,assignment"
# SQLAlchemy 1.x-style Column() descriptors are typed as Column[T] at static-
# analysis time but return T at runtime. Converting models to Mapped[T] is
# out of scope for Phase 1. Scoped ignore lets the rest of --strict apply.
"""
Audit Session service — lifecycle of audit sessions (create, list, get,
start, complete, archive, delete, PDF).

Phase 1 Step 4: extracted from ``compliance/api/audit_routes.py`` so the
route layer becomes thin delegation. This module is HTTP-agnostic: it
raises ``compliance.domain`` errors which the route layer translates to
``HTTPException`` via ``compliance.api._http_errors.translate_domain_errors``.

Checklist and sign-off operations live in
``compliance.services.audit_signoff_service.AuditSignOffService``.
"""

import io
import logging
from datetime import datetime, timezone
from typing import Any, List, Optional
from uuid import uuid4

from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session

from compliance.db.models import (
    AuditResultEnum,
    AuditSessionDB,
    AuditSessionStatusEnum,
    AuditSignOffDB,
    RegulationDB,
    RequirementDB,
)
from compliance.domain import (
    ConflictError,
    DomainError,
    NotFoundError,
    ValidationError,
)
from compliance.schemas.audit_session import (
    AuditSessionDetailResponse,
    AuditSessionResponse,
    AuditSessionSummary,
    AuditStatistics,
    CreateAuditSessionRequest,
)

logger = logging.getLogger(__name__)


class AuditSessionService:
    """Business logic for audit session lifecycle."""

    def __init__(self, db: Session) -> None:
        self.db = db

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    def _get_or_raise(self, session_id: str) -> AuditSessionDB:
        session = (
            self.db.query(AuditSessionDB)
            .filter(AuditSessionDB.id == session_id)
            .first()
        )
        if not session:
            raise NotFoundError(f"Audit session {session_id} not found")
        return session

    @staticmethod
    def _to_summary(s: AuditSessionDB) -> AuditSessionSummary:
        return AuditSessionSummary(
            id=s.id,
            name=s.name,
            auditor_name=s.auditor_name,
            status=s.status.value,
            total_items=s.total_items,
            completed_items=s.completed_items,
            completion_percentage=s.completion_percentage,
            created_at=s.created_at,
            started_at=s.started_at,
            completed_at=s.completed_at,
        )

    @staticmethod
    def _to_response(s: AuditSessionDB) -> AuditSessionResponse:
        return AuditSessionResponse(
            id=s.id,
            name=s.name,
            description=s.description,
            auditor_name=s.auditor_name,
            auditor_email=s.auditor_email,
            auditor_organization=s.auditor_organization,
            status=s.status.value,
            regulation_ids=s.regulation_ids,
            total_items=s.total_items,
            completed_items=s.completed_items,
            compliant_count=s.compliant_count,
            non_compliant_count=s.non_compliant_count,
            completion_percentage=s.completion_percentage,
            created_at=s.created_at,
            started_at=s.started_at,
            completed_at=s.completed_at,
            updated_at=s.updated_at,
        )

    # ------------------------------------------------------------------
    # Commands
    # ------------------------------------------------------------------

    def create(self, request: CreateAuditSessionRequest) -> AuditSessionResponse:
        """Create a new audit session for structured compliance reviews."""
        query = self.db.query(RequirementDB)
        if request.regulation_codes:
            reg_ids = (
                self.db.query(RegulationDB.id)
                .filter(RegulationDB.code.in_(request.regulation_codes))
                .all()
            )
            reg_ids = [r[0] for r in reg_ids]
            query = query.filter(RequirementDB.regulation_id.in_(reg_ids))

        total_items = query.count()

        session = AuditSessionDB(
            id=str(uuid4()),
            name=request.name,
            description=request.description,
            auditor_name=request.auditor_name,
            auditor_email=request.auditor_email,
            auditor_organization=request.auditor_organization,
            status=AuditSessionStatusEnum.DRAFT,
            regulation_ids=request.regulation_codes,
            total_items=total_items,
            completed_items=0,
            compliant_count=0,
            non_compliant_count=0,
        )
        self.db.add(session)
        self.db.commit()
        self.db.refresh(session)
        return self._to_response(session)

    def list(self, status: Optional[str] = None) -> List[AuditSessionSummary]:
        """List all audit sessions, optionally filtered by status."""
        query = self.db.query(AuditSessionDB)
        if status:
            try:
                status_enum = AuditSessionStatusEnum(status)
            except ValueError as exc:
                raise ValidationError(
                    f"Invalid status: {status}. Valid values: draft, in_progress, completed, archived"
                ) from exc
            query = query.filter(AuditSessionDB.status == status_enum)
        sessions = query.order_by(AuditSessionDB.created_at.desc()).all()
        return [self._to_summary(s) for s in sessions]

    def get(self, session_id: str) -> AuditSessionDetailResponse:
        """Get detailed information about a specific audit session."""
        session = self._get_or_raise(session_id)
        signoffs = (
            self.db.query(AuditSignOffDB)
            .filter(AuditSignOffDB.session_id == session_id)
            .all()
        )
        stats = AuditStatistics(
            total=session.total_items,
            compliant=sum(1 for s in signoffs if s.result == AuditResultEnum.COMPLIANT),
            compliant_with_notes=sum(
                1 for s in signoffs if s.result == AuditResultEnum.COMPLIANT_WITH_NOTES
            ),
            non_compliant=sum(
                1 for s in signoffs if s.result == AuditResultEnum.NON_COMPLIANT
            ),
            not_applicable=sum(
                1 for s in signoffs if s.result == AuditResultEnum.NOT_APPLICABLE
            ),
            pending=session.total_items - len(signoffs),
            completion_percentage=session.completion_percentage,
        )
        base = self._to_response(session)
        return AuditSessionDetailResponse(**base.model_dump(), statistics=stats)

    def start(self, session_id: str) -> dict[str, Any]:
        """Move a session from draft to in_progress."""
        session = self._get_or_raise(session_id)
        if session.status != AuditSessionStatusEnum.DRAFT:
            raise ConflictError(
                f"Session cannot be started. Current status: {session.status.value}"
            )
        session.status = AuditSessionStatusEnum.IN_PROGRESS
        session.started_at = datetime.now(timezone.utc)
        self.db.commit()
        return {"success": True, "message": "Audit session started", "status": "in_progress"}

    def complete(self, session_id: str) -> dict[str, Any]:
        """Move a session from in_progress to completed."""
        session = self._get_or_raise(session_id)
        if session.status != AuditSessionStatusEnum.IN_PROGRESS:
            raise ConflictError(
                f"Session cannot be completed. Current status: {session.status.value}"
            )
        session.status = AuditSessionStatusEnum.COMPLETED
        session.completed_at = datetime.now(timezone.utc)
        self.db.commit()
        return {"success": True, "message": "Audit session completed", "status": "completed"}

    def archive(self, session_id: str) -> dict[str, Any]:
        """Archive a completed audit session."""
        session = self._get_or_raise(session_id)
        if session.status != AuditSessionStatusEnum.COMPLETED:
            raise ConflictError(
                f"Only completed sessions can be archived. Current status: {session.status.value}"
            )
        session.status = AuditSessionStatusEnum.ARCHIVED
        self.db.commit()
        return {"success": True, "message": "Audit session archived", "status": "archived"}

    def delete(self, session_id: str) -> dict[str, Any]:
        """Delete a draft or archived session."""
        session = self._get_or_raise(session_id)
        if session.status not in (
            AuditSessionStatusEnum.DRAFT,
            AuditSessionStatusEnum.ARCHIVED,
        ):
            raise ConflictError(
                f"Cannot delete session with status: {session.status.value}. Archive it first."
            )
        self.db.query(AuditSignOffDB).filter(
            AuditSignOffDB.session_id == session_id
        ).delete()
        self.db.delete(session)
        self.db.commit()
        return {"success": True, "message": f"Audit session {session_id} deleted"}

    def generate_pdf(
        self,
        session_id: str,
        language: str,
        include_signatures: bool,
    ) -> StreamingResponse:
        """Generate a PDF audit report and return a streaming response."""
        from compliance.services.audit_pdf_generator import AuditPDFGenerator

        self._get_or_raise(session_id)

        try:
            generator = AuditPDFGenerator(self.db)
            pdf_bytes, filename = generator.generate(
                session_id=session_id,
                language=language,
                include_signatures=include_signatures,
            )
        except Exception as exc:
            logger.error(f"Failed to generate PDF report: {exc}")
            raise DomainError(f"Failed to generate PDF report: {exc}") from exc

        return StreamingResponse(
            io.BytesIO(pdf_bytes),
            media_type="application/pdf",
            headers={"Content-Disposition": f"attachment; filename={filename}"},
        )
