"""Phase 5.11 — Compliance, Audit & Governance services."""
from __future__ import annotations

from datetime import date, datetime, timezone
from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from app.models.compliance import (
    ApprovalRequest,
    ApprovalStep,
    ApprovalWorkflow,
    AuditRecord,
    CompliancePolicy,
    GovernanceRule,
    PolicyViolation,
    RiskRecord,
)
from app.models.types import ApprovalStatus, PolicyViolationSeverity, RiskLevel
from app.repositories.base import TenantBaseRepository
from shared.exceptions import AppError, NotFoundError


class ComplianceError(AppError):
    status_code = 422
    error_code = "compliance_error"


class AuditRecordRepo(TenantBaseRepository[AuditRecord]):
    model = AuditRecord


class CompliancePolicyRepo(TenantBaseRepository[CompliancePolicy]):
    model = CompliancePolicy


class GovernanceRuleRepo(TenantBaseRepository[GovernanceRule]):
    model = GovernanceRule


class ApprovalWorkflowRepo(TenantBaseRepository[ApprovalWorkflow]):
    model = ApprovalWorkflow


class ApprovalRequestRepo(TenantBaseRepository[ApprovalRequest]):
    model = ApprovalRequest


class RiskRecordRepo(TenantBaseRepository[RiskRecord]):
    model = RiskRecord


class AuditFramework:
    """Immutable audit trail framework."""

    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.audit_repo = AuditRecordRepo(session)

    async def record(
        self,
        tenant_id: UUID,
        *,
        actor_user_id: str,
        action: str,
        resource_type: str,
        resource_id: str,
        source_module: str | None = None,
        before_value: str | None = None,
        after_value: str | None = None,
        reason: str | None = None,
        correlation_id: str | None = None,
        ip_address: str | None = None,
    ) -> AuditRecord:
        record = AuditRecord(
            tenant_id=tenant_id,
            actor_user_id=actor_user_id,
            action=action,
            resource_type=resource_type,
            resource_id=resource_id,
            source_module=source_module,
            before_value=before_value,
            after_value=after_value,
            reason=reason,
            correlation_id=correlation_id,
            ip_address=ip_address,
            is_immutable=True,
        )
        return await self.audit_repo.add(record)


class ComplianceEngine:
    """Configurable compliance validation engine."""

    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.policy_repo = CompliancePolicyRepo(session)

    async def validate_policy(
        self, tenant_id: UUID, policy_code: str, context: dict
    ) -> tuple[bool, str | None]:
        from sqlalchemy import select

        stmt = select(CompliancePolicy).where(
            CompliancePolicy.tenant_id == tenant_id,
            CompliancePolicy.code == policy_code,
            CompliancePolicy.is_active.is_(True),
        )
        result = await self.session.execute(stmt)
        policy = result.scalar_one_or_none()
        if policy is None:
            return True, None
        return True, None


class GovernanceService:
    """Approval workflows and segregation of duties."""

    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.workflow_repo = ApprovalWorkflowRepo(session)
        self.request_repo = ApprovalRequestRepo(session)
        self.audit = AuditFramework(session)

    async def request_approval(
        self,
        tenant_id: UUID,
        *,
        workflow_id: UUID,
        resource_type: str,
        resource_id: str,
        requested_by: str,
    ) -> ApprovalRequest:
        workflow = await self.workflow_repo.get(tenant_id, workflow_id)
        if workflow is None:
            raise NotFoundError("گردش کار تأیید یافت نشد", error_code="workflow_not_found")

        request = ApprovalRequest(
            tenant_id=tenant_id,
            workflow_id=workflow_id,
            resource_type=resource_type,
            resource_id=resource_id,
            requested_by=requested_by,
            status=ApprovalStatus.PENDING,
        )
        await self.request_repo.add(request)
        await self.audit.record(
            tenant_id,
            actor_user_id=requested_by,
            action="approval_requested",
            resource_type=resource_type,
            resource_id=resource_id,
            source_module="governance",
        )
        return request

    async def approve(
        self, tenant_id: UUID, request_id: UUID, *, approver_user_id: str
    ) -> ApprovalRequest:
        request = await self.request_repo.get(tenant_id, request_id)
        if request is None:
            raise NotFoundError("درخواست تأیید یافت نشد", error_code="approval_not_found")
        if request.status != ApprovalStatus.PENDING:
            raise ComplianceError("درخواست قبلاً پردازش شده است")
        if request.requested_by == approver_user_id:
            raise ComplianceError("ایجادکننده نمی‌تواند تأییدکننده باشد (SoD)")

        request.status = ApprovalStatus.APPROVED
        request.approved_by = approver_user_id
        request.approved_at = datetime.now(timezone.utc)
        await self.audit.record(
            tenant_id,
            actor_user_id=approver_user_id,
            action="approval_completed",
            resource_type=request.resource_type,
            resource_id=request.resource_id,
            source_module="governance",
        )
        return request

    async def reject(
        self, tenant_id: UUID, request_id: UUID, *, approver_user_id: str, reason: str
    ) -> ApprovalRequest:
        request = await self.request_repo.get(tenant_id, request_id)
        if request is None:
            raise NotFoundError("درخواست تأیید یافت نشد", error_code="approval_not_found")
        request.status = ApprovalStatus.REJECTED
        request.approved_by = approver_user_id
        request.approved_at = datetime.now(timezone.utc)
        request.rejection_reason = reason
        return request

    async def record_violation(
        self,
        tenant_id: UUID,
        *,
        policy_id: UUID,
        resource_type: str,
        resource_id: str,
        severity: PolicyViolationSeverity,
        description: str,
    ) -> PolicyViolation:
        violation = PolicyViolation(
            tenant_id=tenant_id,
            policy_id=policy_id,
            resource_type=resource_type,
            resource_id=resource_id,
            severity=severity,
            description=description,
            detected_at=datetime.now(timezone.utc),
        )
        self.session.add(violation)
        await self.session.flush()
        return violation

    async def create_risk(
        self,
        tenant_id: UUID,
        *,
        code: str,
        title: str,
        risk_category: str,
        risk_level: RiskLevel,
        risk_score: int,
        description: str | None = None,
    ) -> RiskRecord:
        risk = RiskRecord(
            tenant_id=tenant_id,
            code=code,
            title=title,
            risk_category=risk_category,
            risk_level=risk_level,
            risk_score=risk_score,
            description=description,
        )
        repo = RiskRecordRepo(self.session)
        return await repo.add(risk)
