# mypy: disable-error-code="arg-type,assignment"
# See compliance/services/audit_session_service.py for rationale — SQLAlchemy
# 1.x Column() descriptors are Column[T] statically but T at runtime.
"""
Audit Sign-Off service — audit checklist retrieval and per-requirement sign-off
operations.

Phase 1 Step 4: extracted from ``compliance/api/audit_routes.py``. HTTP-agnostic;
raises ``compliance.domain`` errors translated at the route layer.
"""

import hashlib
from datetime import datetime, timezone
from typing import Optional
from uuid import uuid4

from sqlalchemy import func
from sqlalchemy.orm import Session

from compliance.db.models import (
    AuditResultEnum,
    AuditSessionDB,
    AuditSessionStatusEnum,
    AuditSignOffDB,
    ControlMappingDB,
    RegulationDB,
    RequirementDB,
)
from compliance.domain import ConflictError, NotFoundError, ValidationError
from compliance.schemas.audit_session import (
    AuditChecklistItem,
    AuditChecklistResponse,
    AuditSessionSummary,
    AuditStatistics,
    SignOffRequest,
    SignOffResponse,
)
from compliance.schemas.common import PaginationMeta


class AuditSignOffService:
    """Business logic for audit checklist & per-requirement sign-offs."""

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

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

    def _get_session_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 _signoff_to_response(signoff: AuditSignOffDB) -> SignOffResponse:
        return SignOffResponse(
            id=signoff.id,
            session_id=signoff.session_id,
            requirement_id=signoff.requirement_id,
            result=signoff.result.value,
            notes=signoff.notes,
            is_signed=signoff.signature_hash is not None,
            signature_hash=signoff.signature_hash,
            signed_at=signoff.signed_at,
            signed_by=signoff.signed_by,
            created_at=signoff.created_at,
            updated_at=signoff.updated_at,
        )

    @staticmethod
    def _compute_stats(
        total: int, signoffs: list[AuditSignOffDB], completion_percentage: float
    ) -> AuditStatistics:
        return AuditStatistics(
            total=total,
            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=total - len(signoffs),
            completion_percentage=completion_percentage,
        )

    # ------------------------------------------------------------------
    # Queries
    # ------------------------------------------------------------------

    def get_checklist(
        self,
        session_id: str,
        page: int,
        page_size: int,
        status_filter: Optional[str],
        regulation_filter: Optional[str],
        search: Optional[str],
    ) -> AuditChecklistResponse:
        """Return the paginated audit checklist with per-requirement sign-off status."""
        session = self._get_session_or_raise(session_id)

        query = self.db.query(RequirementDB).join(RegulationDB)
        if session.regulation_ids:
            query = query.filter(RegulationDB.code.in_(session.regulation_ids))
        if regulation_filter:
            query = query.filter(RegulationDB.code == regulation_filter)
        if search:
            term = f"%{search}%"
            query = query.filter(
                (RequirementDB.title.ilike(term))
                | (RequirementDB.article.ilike(term))
                | (RequirementDB.description.ilike(term))
            )

        total_count = query.count()
        requirements = (
            query.order_by(RegulationDB.code, RequirementDB.article)
            .offset((page - 1) * page_size)
            .limit(page_size)
            .all()
        )

        req_ids = [r.id for r in requirements]
        signoffs = (
            self.db.query(AuditSignOffDB)
            .filter(AuditSignOffDB.session_id == session_id)
            .filter(AuditSignOffDB.requirement_id.in_(req_ids))
            .all()
        )
        signoff_map = {s.requirement_id: s for s in signoffs}

        mapping_counts = (
            self.db.query(ControlMappingDB.requirement_id, func.count(ControlMappingDB.id))
            .filter(ControlMappingDB.requirement_id.in_(req_ids))
            .group_by(ControlMappingDB.requirement_id)
            .all()
        )
        mapping_count_map: dict[str, int] = dict(mapping_counts)

        items: list[AuditChecklistItem] = []
        for req in requirements:
            signoff = signoff_map.get(req.id)
            if status_filter:
                if status_filter == "pending" and signoff is not None:
                    continue
                if status_filter != "pending" and (
                    signoff is None or signoff.result.value != status_filter
                ):
                    continue
            items.append(
                AuditChecklistItem(
                    requirement_id=req.id,
                    regulation_code=req.regulation.code,
                    article=req.article,
                    paragraph=req.paragraph,
                    title=req.title,
                    description=req.description,
                    current_result=signoff.result.value if signoff else "pending",
                    notes=signoff.notes if signoff else None,
                    is_signed=signoff.signature_hash is not None if signoff else False,
                    signed_at=signoff.signed_at if signoff else None,
                    signed_by=signoff.signed_by if signoff else None,
                    evidence_count=0,  # TODO: Add evidence count
                    controls_mapped=mapping_count_map.get(str(req.id), 0),
                    implementation_status=req.implementation_status,
                    priority=req.priority,
                )
            )

        all_signoffs = (
            self.db.query(AuditSignOffDB)
            .filter(AuditSignOffDB.session_id == session_id)
            .all()
        )
        stats = self._compute_stats(
            session.total_items, all_signoffs, session.completion_percentage
        )

        return AuditChecklistResponse(
            session=AuditSessionSummary(
                id=session.id,
                name=session.name,
                auditor_name=session.auditor_name,
                status=session.status.value,
                total_items=session.total_items,
                completed_items=session.completed_items,
                completion_percentage=session.completion_percentage,
                created_at=session.created_at,
                started_at=session.started_at,
                completed_at=session.completed_at,
            ),
            items=items,
            pagination=PaginationMeta(
                page=page,
                page_size=page_size,
                total=total_count,
                total_pages=(total_count + page_size - 1) // page_size,
                has_next=page * page_size < total_count,
                has_prev=page > 1,
            ),
            statistics=stats,
        )

    def get_sign_off(self, session_id: str, requirement_id: str) -> SignOffResponse:
        """Return a single sign-off record for (session, requirement)."""
        signoff = (
            self.db.query(AuditSignOffDB)
            .filter(AuditSignOffDB.session_id == session_id)
            .filter(AuditSignOffDB.requirement_id == requirement_id)
            .first()
        )
        if not signoff:
            raise NotFoundError(
                f"No sign-off found for requirement {requirement_id} in session {session_id}"
            )
        return self._signoff_to_response(signoff)

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

    def sign_off(
        self,
        session_id: str,
        requirement_id: str,
        request: SignOffRequest,
    ) -> SignOffResponse:
        """Create or update a sign-off; optionally produce a SHA-256 digital signature."""
        session = self._get_session_or_raise(session_id)
        if session.status not in (
            AuditSessionStatusEnum.DRAFT,
            AuditSessionStatusEnum.IN_PROGRESS,
        ):
            raise ConflictError(
                f"Cannot sign off items in session with status: {session.status.value}"
            )

        requirement = (
            self.db.query(RequirementDB)
            .filter(RequirementDB.id == requirement_id)
            .first()
        )
        if not requirement:
            raise NotFoundError(f"Requirement {requirement_id} not found")

        try:
            result_enum = AuditResultEnum(request.result)
        except ValueError as exc:
            raise ValidationError(
                "Invalid result: "
                f"{request.result}. Valid values: compliant, compliant_notes, "
                "non_compliant, not_applicable, pending"
            ) from exc

        signoff = (
            self.db.query(AuditSignOffDB)
            .filter(AuditSignOffDB.session_id == session_id)
            .filter(AuditSignOffDB.requirement_id == requirement_id)
            .first()
        )
        was_new = signoff is None
        old_result = signoff.result if signoff else None

        if signoff:
            signoff.result = result_enum
            signoff.notes = request.notes
            signoff.updated_at = datetime.now(timezone.utc)
        else:
            signoff = AuditSignOffDB(
                id=str(uuid4()),
                session_id=session_id,
                requirement_id=requirement_id,
                result=result_enum,
                notes=request.notes,
            )
            self.db.add(signoff)

        if request.sign:
            timestamp = datetime.now(timezone.utc).isoformat()
            data = (
                f"{result_enum.value}|{requirement_id}|{session.auditor_name}|{timestamp}"
            )
            signoff.signature_hash = hashlib.sha256(data.encode()).hexdigest()
            signoff.signed_at = datetime.now(timezone.utc)
            signoff.signed_by = session.auditor_name

        if was_new:
            session.completed_items += 1

        if old_result != result_enum:
            if old_result in (
                AuditResultEnum.COMPLIANT,
                AuditResultEnum.COMPLIANT_WITH_NOTES,
            ):
                session.compliant_count = max(0, session.compliant_count - 1)
            elif old_result == AuditResultEnum.NON_COMPLIANT:
                session.non_compliant_count = max(0, session.non_compliant_count - 1)

            if result_enum in (
                AuditResultEnum.COMPLIANT,
                AuditResultEnum.COMPLIANT_WITH_NOTES,
            ):
                session.compliant_count += 1
            elif result_enum == AuditResultEnum.NON_COMPLIANT:
                session.non_compliant_count += 1

        if session.status == AuditSessionStatusEnum.DRAFT:
            session.status = AuditSessionStatusEnum.IN_PROGRESS
            session.started_at = datetime.now(timezone.utc)

        self.db.commit()
        self.db.refresh(signoff)
        return self._signoff_to_response(signoff)
