# mypy: disable-error-code="arg-type,assignment,union-attr,no-any-return,attr-defined,index,call-overload,type-arg,var-annotated,misc,call-arg,return-value"
"""
ISMS Findings & CAPA service -- Audit Findings and Corrective Actions.

Phase 1 Step 4: extracted from ``compliance.api.isms_routes``.
"""

from datetime import datetime, date, timezone
from typing import Optional

from sqlalchemy.orm import Session

from compliance.db.models import (
    AuditFindingDB,
    CorrectiveActionDB,
    InternalAuditDB,
    FindingTypeEnum,
    FindingStatusEnum,
    CAPATypeEnum,
)
from compliance.domain import NotFoundError, ConflictError, ValidationError
from compliance.services.isms_governance_service import generate_id, log_audit_trail


# ============================================================================
# Audit Findings
# ============================================================================


class AuditFindingService:
    """Business logic for Audit Findings."""

    @staticmethod
    def list_findings(
        db: Session,
        finding_type: Optional[str] = None,
        status: Optional[str] = None,
        internal_audit_id: Optional[str] = None,
    ) -> dict:
        query = db.query(AuditFindingDB)
        if finding_type:
            query = query.filter(AuditFindingDB.finding_type == finding_type)
        if status:
            query = query.filter(AuditFindingDB.status == status)
        if internal_audit_id:
            query = query.filter(AuditFindingDB.internal_audit_id == internal_audit_id)
        findings = query.order_by(AuditFindingDB.identified_date.desc()).all()
        major_count = sum(1 for f in findings if f.finding_type == FindingTypeEnum.MAJOR)
        minor_count = sum(1 for f in findings if f.finding_type == FindingTypeEnum.MINOR)
        ofi_count = sum(1 for f in findings if f.finding_type == FindingTypeEnum.OFI)
        open_count = sum(1 for f in findings if f.status != FindingStatusEnum.CLOSED)
        return {
            "findings": findings,
            "total": len(findings),
            "major_count": major_count,
            "minor_count": minor_count,
            "ofi_count": ofi_count,
            "open_count": open_count,
        }

    @staticmethod
    def create(db: Session, data: dict) -> AuditFindingDB:
        year = date.today().year
        existing_count = (
            db.query(AuditFindingDB)
            .filter(AuditFindingDB.finding_id.like(f"FIND-{year}-%"))
            .count()
        )
        finding_id = f"FIND-{year}-{existing_count + 1:03d}"

        internal_audit_id = data.pop("internal_audit_id", None)
        audit_session_id = data.pop("audit_session_id", None)
        finding_type_str = data.pop("finding_type")

        finding = AuditFindingDB(
            id=generate_id(),
            finding_id=finding_id,
            audit_session_id=audit_session_id,
            internal_audit_id=internal_audit_id,
            finding_type=FindingTypeEnum(finding_type_str),
            status=FindingStatusEnum.OPEN,
            **data,
        )
        db.add(finding)

        # Update internal audit counts if linked
        if internal_audit_id:
            audit = (
                db.query(InternalAuditDB)
                .filter(InternalAuditDB.id == internal_audit_id)
                .first()
            )
            if audit:
                audit.total_findings = (audit.total_findings or 0) + 1
                if finding_type_str == "major":
                    audit.major_findings = (audit.major_findings or 0) + 1
                elif finding_type_str == "minor":
                    audit.minor_findings = (audit.minor_findings or 0) + 1
                elif finding_type_str == "ofi":
                    audit.ofi_count = (audit.ofi_count or 0) + 1
                elif finding_type_str == "positive":
                    audit.positive_observations = (audit.positive_observations or 0) + 1

        log_audit_trail(db, "audit_finding", finding.id, finding_id, "create", data.get("auditor", "unknown"))
        db.commit()
        db.refresh(finding)
        return finding

    @staticmethod
    def update(db: Session, finding_id: str, data: dict, updated_by: str) -> AuditFindingDB:
        finding = (
            db.query(AuditFindingDB)
            .filter((AuditFindingDB.id == finding_id) | (AuditFindingDB.finding_id == finding_id))
            .first()
        )
        if not finding:
            raise NotFoundError("Finding not found")
        for field, value in data.items():
            if field == "status" and value:
                setattr(finding, field, FindingStatusEnum(value))
            else:
                setattr(finding, field, value)
        log_audit_trail(db, "audit_finding", finding.id, finding.finding_id, "update", updated_by)
        db.commit()
        db.refresh(finding)
        return finding

    @staticmethod
    def close(
        db: Session,
        finding_id: str,
        closure_notes: str,
        closed_by: str,
        verification_method: str,
        verification_evidence: str,
    ) -> AuditFindingDB:
        finding = (
            db.query(AuditFindingDB)
            .filter((AuditFindingDB.id == finding_id) | (AuditFindingDB.finding_id == finding_id))
            .first()
        )
        if not finding:
            raise NotFoundError("Finding not found")
        open_capas = (
            db.query(CorrectiveActionDB)
            .filter(
                CorrectiveActionDB.finding_id == finding.id,
                CorrectiveActionDB.status != "verified",
            )
            .count()
        )
        if open_capas > 0:
            raise ValidationError(f"Cannot close finding: {open_capas} CAPA(s) not yet verified")
        finding.status = FindingStatusEnum.CLOSED
        finding.closed_date = date.today()
        finding.closure_notes = closure_notes
        finding.closed_by = closed_by
        finding.verification_method = verification_method
        finding.verification_evidence = verification_evidence
        finding.verified_by = closed_by
        finding.verified_at = datetime.now(timezone.utc)
        log_audit_trail(db, "audit_finding", finding.id, finding.finding_id, "close", closed_by)
        db.commit()
        db.refresh(finding)
        return finding


# ============================================================================
# Corrective Actions (CAPA)
# ============================================================================


class CAPAService:
    """Business logic for Corrective / Preventive Actions."""

    @staticmethod
    def list_capas(
        db: Session,
        finding_id: Optional[str] = None,
        status: Optional[str] = None,
        assigned_to: Optional[str] = None,
    ) -> tuple:
        query = db.query(CorrectiveActionDB)
        if finding_id:
            query = query.filter(CorrectiveActionDB.finding_id == finding_id)
        if status:
            query = query.filter(CorrectiveActionDB.status == status)
        if assigned_to:
            query = query.filter(CorrectiveActionDB.assigned_to == assigned_to)
        actions = query.order_by(CorrectiveActionDB.planned_completion).all()
        return actions, len(actions)

    @staticmethod
    def create(db: Session, data: dict, created_by: str) -> CorrectiveActionDB:
        finding = db.query(AuditFindingDB).filter(AuditFindingDB.id == data["finding_id"]).first()
        if not finding:
            raise NotFoundError("Finding not found")
        year = date.today().year
        existing_count = (
            db.query(CorrectiveActionDB)
            .filter(CorrectiveActionDB.capa_id.like(f"CAPA-{year}-%"))
            .count()
        )
        capa_id = f"CAPA-{year}-{existing_count + 1:03d}"
        capa_type_str = data.pop("capa_type")
        capa = CorrectiveActionDB(
            id=generate_id(),
            capa_id=capa_id,
            capa_type=CAPATypeEnum(capa_type_str),
            status="planned",
            **data,
        )
        db.add(capa)
        finding.status = FindingStatusEnum.CORRECTIVE_ACTION_PENDING
        log_audit_trail(db, "capa", capa.id, capa_id, "create", created_by)
        db.commit()
        db.refresh(capa)
        return capa

    @staticmethod
    def update(db: Session, capa_id: str, data: dict, updated_by: str) -> CorrectiveActionDB:
        capa = (
            db.query(CorrectiveActionDB)
            .filter((CorrectiveActionDB.id == capa_id) | (CorrectiveActionDB.capa_id == capa_id))
            .first()
        )
        if not capa:
            raise NotFoundError("CAPA not found")
        for field, value in data.items():
            setattr(capa, field, value)
        if capa.status == "completed" and not capa.actual_completion:
            capa.actual_completion = date.today()
        log_audit_trail(db, "capa", capa.id, capa.capa_id, "update", updated_by)
        db.commit()
        db.refresh(capa)
        return capa

    @staticmethod
    def verify(
        db: Session,
        capa_id: str,
        verified_by: str,
        is_effective: bool,
        effectiveness_notes: str,
    ) -> CorrectiveActionDB:
        capa = (
            db.query(CorrectiveActionDB)
            .filter((CorrectiveActionDB.id == capa_id) | (CorrectiveActionDB.capa_id == capa_id))
            .first()
        )
        if not capa:
            raise NotFoundError("CAPA not found")
        if capa.status != "completed":
            raise ValidationError("CAPA must be completed before verification")
        capa.effectiveness_verified = is_effective
        capa.effectiveness_verification_date = date.today()
        capa.effectiveness_notes = effectiveness_notes
        capa.status = "verified" if is_effective else "completed"
        if is_effective:
            finding = db.query(AuditFindingDB).filter(AuditFindingDB.id == capa.finding_id).first()
            if finding:
                unverified = (
                    db.query(CorrectiveActionDB)
                    .filter(
                        CorrectiveActionDB.finding_id == finding.id,
                        CorrectiveActionDB.id != capa.id,
                        CorrectiveActionDB.status != "verified",
                    )
                    .count()
                )
                if unverified == 0:
                    finding.status = FindingStatusEnum.VERIFICATION_PENDING
        log_audit_trail(db, "capa", capa.id, capa.capa_id, "verify", verified_by)
        db.commit()
        db.refresh(capa)
        return capa
