# 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"
"""
DSR workflow service — status changes, identity verification, assignment,
completion, rejection, communications, exception checks, and templates.

Phase 1 Step 4: extracted from ``compliance.api.dsr_routes``. CRUD, stats,
export, and deadline processing live in ``compliance.services.dsr_service``.
"""

import uuid
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

from sqlalchemy import or_
from sqlalchemy.orm import Session

from compliance.db.dsr_models import (
    DSRCommunicationDB,
    DSRExceptionCheckDB,
    DSRRequestDB,
    DSRTemplateDB,
    DSRTemplateVersionDB,
)
from compliance.domain import NotFoundError, ValidationError
from compliance.schemas.dsr import (
    AssignRequest,
    CompleteDSR,
    CreateTemplateVersion,
    ExtendDeadline,
    RejectDSR,
    SendCommunication,
    StatusChange,
    UpdateExceptionCheck,
    VerifyIdentity,
)
from compliance.services.dsr_service import (
    ART17_EXCEPTIONS,
    VALID_STATUSES,
    _dsr_to_dict,
    _get_dsr_or_404,
    _record_history,
)


class DSRWorkflowService:
    """Workflow actions for DSRs: status, identity, assign, complete, reject,
    communications, exception checks, templates."""

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

    # -- Status change -------------------------------------------------------

    def change_status(
        self, dsr_id: str, body: StatusChange, tenant_id: str,
    ) -> Dict[str, Any]:
        if body.status not in VALID_STATUSES:
            raise ValidationError(f"Invalid status: {body.status}")
        dsr = _get_dsr_or_404(self._db, dsr_id, tenant_id)
        _record_history(self._db, dsr, body.status, comment=body.comment)
        dsr.status = body.status
        dsr.updated_at = datetime.now(timezone.utc)
        self._db.commit()
        self._db.refresh(dsr)
        return _dsr_to_dict(dsr)

    # -- Identity verification -----------------------------------------------

    def verify_identity(
        self, dsr_id: str, body: VerifyIdentity, tenant_id: str,
    ) -> Dict[str, Any]:
        dsr = _get_dsr_or_404(self._db, dsr_id, tenant_id)
        now = datetime.now(timezone.utc)
        dsr.identity_verified = True
        dsr.verification_method = body.method
        dsr.verified_at = now
        dsr.verified_by = "admin"
        dsr.verification_notes = body.notes
        dsr.verification_document_ref = body.document_ref

        if dsr.status == "identity_verification":
            _record_history(self._db, dsr, "processing", comment="Identitaet verifiziert")
            dsr.status = "processing"
        elif dsr.status == "intake":
            _record_history(self._db, dsr, "identity_verification", comment="Identitaet verifiziert")
            dsr.status = "identity_verification"

        dsr.updated_at = now
        self._db.commit()
        self._db.refresh(dsr)
        return _dsr_to_dict(dsr)

    # -- Assignment ----------------------------------------------------------

    def assign(
        self, dsr_id: str, body: AssignRequest, tenant_id: str,
    ) -> Dict[str, Any]:
        dsr = _get_dsr_or_404(self._db, dsr_id, tenant_id)
        dsr.assigned_to = body.assignee_id
        dsr.assigned_at = datetime.now(timezone.utc)
        dsr.assigned_by = "admin"
        dsr.updated_at = datetime.now(timezone.utc)
        self._db.commit()
        self._db.refresh(dsr)
        return _dsr_to_dict(dsr)

    # -- Extend deadline -----------------------------------------------------

    def extend_deadline(
        self, dsr_id: str, body: ExtendDeadline, tenant_id: str,
    ) -> Dict[str, Any]:
        dsr = _get_dsr_or_404(self._db, dsr_id, tenant_id)
        if dsr.status in ("completed", "rejected", "cancelled"):
            raise ValidationError("Cannot extend deadline for closed DSR")
        now = datetime.now(timezone.utc)
        from datetime import timedelta
        current_deadline = dsr.extended_deadline_at or dsr.deadline_at
        new_deadline = current_deadline + timedelta(days=body.days or 60)
        dsr.extended_deadline_at = new_deadline
        dsr.extension_reason = body.reason
        dsr.extension_approved_by = "admin"
        dsr.extension_approved_at = now
        dsr.updated_at = now
        _record_history(self._db, dsr, dsr.status, comment=f"Frist verlaengert: {body.reason}")
        self._db.commit()
        self._db.refresh(dsr)
        return _dsr_to_dict(dsr)

    # -- Complete ------------------------------------------------------------

    def complete(
        self, dsr_id: str, body: CompleteDSR, tenant_id: str,
    ) -> Dict[str, Any]:
        dsr = _get_dsr_or_404(self._db, dsr_id, tenant_id)
        if dsr.status in ("completed", "cancelled"):
            raise ValidationError("DSR already completed or cancelled")
        now = datetime.now(timezone.utc)
        _record_history(self._db, dsr, "completed", comment=body.summary)
        dsr.status = "completed"
        dsr.completed_at = now
        dsr.completion_notes = body.summary
        if body.result_data:
            dsr.data_export = body.result_data
        dsr.updated_at = now

        # Phase 3: Auto-delete banner consents on Art. 17 erasure
        banner_result = None
        if dsr.request_type == "erasure" and dsr.requester_email:
            from compliance.services.banner_dsr_service import BannerDSRService
            banner_svc = BannerDSRService(self._db)
            banner_result = banner_svc.delete_consents_by_email(
                tenant_id, dsr.requester_email,
            )

        # Phase 3: Include banner consents in data export for access/portability
        if dsr.request_type in ("access", "portability") and dsr.requester_email:
            from compliance.services.banner_dsr_service import BannerDSRService
            banner_svc = BannerDSRService(self._db)
            export = banner_svc.export_for_dsr(tenant_id, dsr.requester_email)
            if export.get("banner_consents"):
                existing_export = dsr.data_export or {}
                if isinstance(existing_export, dict):
                    existing_export["banner_consents"] = export
                    dsr.data_export = existing_export

        self._db.commit()
        self._db.refresh(dsr)
        result = _dsr_to_dict(dsr)
        if banner_result:
            result["banner_consents_deleted"] = banner_result["deleted"]
        return result

    # -- Reject --------------------------------------------------------------

    def reject(
        self, dsr_id: str, body: RejectDSR, tenant_id: str,
    ) -> Dict[str, Any]:
        dsr = _get_dsr_or_404(self._db, dsr_id, tenant_id)
        if dsr.status in ("completed", "rejected", "cancelled"):
            raise ValidationError("DSR already closed")
        now = datetime.now(timezone.utc)
        _record_history(self._db, dsr, "rejected", comment=f"{body.reason} ({body.legal_basis})")
        dsr.status = "rejected"
        dsr.rejection_reason = body.reason
        dsr.rejection_legal_basis = body.legal_basis
        dsr.completed_at = now
        dsr.updated_at = now
        self._db.commit()
        self._db.refresh(dsr)
        return _dsr_to_dict(dsr)

    # -- History -------------------------------------------------------------

    def get_history(self, dsr_id: str, tenant_id: str) -> List[Dict[str, Any]]:
        from compliance.db.dsr_models import DSRStatusHistoryDB
        dsr = _get_dsr_or_404(self._db, dsr_id, tenant_id)
        entries = self._db.query(DSRStatusHistoryDB).filter(
            DSRStatusHistoryDB.dsr_id == dsr.id,
        ).order_by(DSRStatusHistoryDB.created_at.desc()).all()
        return [
            {
                "id": str(e.id),
                "dsr_id": str(e.dsr_id),
                "previous_status": e.previous_status,
                "new_status": e.new_status,
                "changed_by": e.changed_by,
                "comment": e.comment,
                "created_at": e.created_at.isoformat() if e.created_at else None,
            }
            for e in entries
        ]

    # -- Communications ------------------------------------------------------

    def get_communications(self, dsr_id: str, tenant_id: str) -> List[Dict[str, Any]]:
        dsr = _get_dsr_or_404(self._db, dsr_id, tenant_id)
        comms = self._db.query(DSRCommunicationDB).filter(
            DSRCommunicationDB.dsr_id == dsr.id,
        ).order_by(DSRCommunicationDB.created_at.desc()).all()
        return [
            {
                "id": str(c.id),
                "dsr_id": str(c.dsr_id),
                "communication_type": c.communication_type,
                "channel": c.channel,
                "subject": c.subject,
                "content": c.content,
                "template_used": c.template_used,
                "attachments": c.attachments or [],
                "sent_at": c.sent_at.isoformat() if c.sent_at else None,
                "sent_by": c.sent_by,
                "received_at": c.received_at.isoformat() if c.received_at else None,
                "created_at": c.created_at.isoformat() if c.created_at else None,
                "created_by": c.created_by,
            }
            for c in comms
        ]

    def send_communication(
        self, dsr_id: str, body: SendCommunication, tenant_id: str,
    ) -> Dict[str, Any]:
        dsr = _get_dsr_or_404(self._db, dsr_id, tenant_id)
        now = datetime.now(timezone.utc)
        comm = DSRCommunicationDB(
            tenant_id=uuid.UUID(tenant_id),
            dsr_id=dsr.id,
            communication_type=body.communication_type,
            channel=body.channel,
            subject=body.subject,
            content=body.content,
            template_used=body.template_used,
            sent_at=now if body.communication_type == "outgoing" else None,
            sent_by="admin" if body.communication_type == "outgoing" else None,
            received_at=now if body.communication_type == "incoming" else None,
            created_at=now,
        )
        self._db.add(comm)
        self._db.commit()
        self._db.refresh(comm)
        return {
            "id": str(comm.id),
            "dsr_id": str(comm.dsr_id),
            "communication_type": comm.communication_type,
            "channel": comm.channel,
            "subject": comm.subject,
            "content": comm.content,
            "sent_at": comm.sent_at.isoformat() if comm.sent_at else None,
            "created_at": comm.created_at.isoformat() if comm.created_at else None,
        }

    # -- Exception checks ----------------------------------------------------

    def get_exception_checks(self, dsr_id: str, tenant_id: str) -> List[Dict[str, Any]]:
        dsr = _get_dsr_or_404(self._db, dsr_id, tenant_id)
        checks = self._db.query(DSRExceptionCheckDB).filter(
            DSRExceptionCheckDB.dsr_id == dsr.id,
        ).order_by(DSRExceptionCheckDB.check_code).all()
        return [
            {
                "id": str(c.id),
                "dsr_id": str(c.dsr_id),
                "check_code": c.check_code,
                "article": c.article,
                "label": c.label,
                "description": c.description,
                "applies": c.applies,
                "notes": c.notes,
                "checked_by": c.checked_by,
                "checked_at": c.checked_at.isoformat() if c.checked_at else None,
            }
            for c in checks
        ]

    def init_exception_checks(self, dsr_id: str, tenant_id: str) -> List[Dict[str, Any]]:
        dsr = _get_dsr_or_404(self._db, dsr_id, tenant_id)
        if dsr.request_type != "erasure":
            raise ValidationError("Exception checks only for erasure requests")
        existing = self._db.query(DSRExceptionCheckDB).filter(
            DSRExceptionCheckDB.dsr_id == dsr.id,
        ).count()
        if existing > 0:
            raise ValidationError("Exception checks already initialized")

        checks = []
        for exc in ART17_EXCEPTIONS:
            check = DSRExceptionCheckDB(
                tenant_id=uuid.UUID(tenant_id),
                dsr_id=dsr.id,
                check_code=exc["check_code"],
                article=exc["article"],
                label=exc["label"],
                description=exc["description"],
            )
            self._db.add(check)
            checks.append(check)
        self._db.commit()
        return [
            {
                "id": str(c.id),
                "dsr_id": str(c.dsr_id),
                "check_code": c.check_code,
                "article": c.article,
                "label": c.label,
                "description": c.description,
                "applies": c.applies,
                "notes": c.notes,
            }
            for c in checks
        ]

    def update_exception_check(
        self, dsr_id: str, check_id: str, body: UpdateExceptionCheck, tenant_id: str,
    ) -> Dict[str, Any]:
        dsr = _get_dsr_or_404(self._db, dsr_id, tenant_id)
        try:
            cid = uuid.UUID(check_id)
        except ValueError:
            raise ValidationError("Invalid check ID")
        check = self._db.query(DSRExceptionCheckDB).filter(
            DSRExceptionCheckDB.id == cid,
            DSRExceptionCheckDB.dsr_id == dsr.id,
        ).first()
        if not check:
            raise NotFoundError("Exception check not found")
        check.applies = body.applies
        check.notes = body.notes
        check.checked_by = "admin"
        check.checked_at = datetime.now(timezone.utc)
        self._db.commit()
        self._db.refresh(check)
        return {
            "id": str(check.id),
            "dsr_id": str(check.dsr_id),
            "check_code": check.check_code,
            "article": check.article,
            "label": check.label,
            "description": check.description,
            "applies": check.applies,
            "notes": check.notes,
            "checked_by": check.checked_by,
            "checked_at": check.checked_at.isoformat() if check.checked_at else None,
        }

    # -- Templates -----------------------------------------------------------

    def get_templates(self, tenant_id: str) -> List[Dict[str, Any]]:
        templates = self._db.query(DSRTemplateDB).filter(
            DSRTemplateDB.tenant_id == uuid.UUID(tenant_id),
        ).order_by(DSRTemplateDB.template_type).all()
        return [
            {
                "id": str(t.id),
                "name": t.name,
                "template_type": t.template_type,
                "request_type": t.request_type,
                "language": t.language,
                "is_active": t.is_active,
                "created_at": t.created_at.isoformat() if t.created_at else None,
                "updated_at": t.updated_at.isoformat() if t.updated_at else None,
            }
            for t in templates
        ]

    def get_published_templates(
        self, tenant_id: str, *, request_type: Optional[str] = None, language: str = "de",
    ) -> List[Dict[str, Any]]:
        query = self._db.query(DSRTemplateDB).filter(
            DSRTemplateDB.tenant_id == uuid.UUID(tenant_id),
            DSRTemplateDB.is_active,
            DSRTemplateDB.language == language,
        )
        if request_type:
            query = query.filter(
                or_(
                    DSRTemplateDB.request_type == request_type,
                    DSRTemplateDB.request_type.is_(None),
                )
            )
        templates = query.all()
        result = []
        for t in templates:
            latest = self._db.query(DSRTemplateVersionDB).filter(
                DSRTemplateVersionDB.template_id == t.id,
                DSRTemplateVersionDB.status == "published",
            ).order_by(DSRTemplateVersionDB.created_at.desc()).first()
            result.append({
                "id": str(t.id),
                "name": t.name,
                "template_type": t.template_type,
                "request_type": t.request_type,
                "language": t.language,
                "latest_version": {
                    "id": str(latest.id),
                    "version": latest.version,
                    "subject": latest.subject,
                    "body_html": latest.body_html,
                    "body_text": latest.body_text,
                } if latest else None,
            })
        return result

    def get_template_versions(self, template_id: str, tenant_id: str) -> List[Dict[str, Any]]:
        try:
            tid = uuid.UUID(template_id)
        except ValueError:
            raise ValidationError("Invalid template ID")
        template = self._db.query(DSRTemplateDB).filter(
            DSRTemplateDB.id == tid,
            DSRTemplateDB.tenant_id == uuid.UUID(tenant_id),
        ).first()
        if not template:
            raise NotFoundError("Template not found")
        versions = self._db.query(DSRTemplateVersionDB).filter(
            DSRTemplateVersionDB.template_id == tid,
        ).order_by(DSRTemplateVersionDB.created_at.desc()).all()
        return [
            {
                "id": str(v.id),
                "template_id": str(v.template_id),
                "version": v.version,
                "subject": v.subject,
                "body_html": v.body_html,
                "body_text": v.body_text,
                "status": v.status,
                "published_at": v.published_at.isoformat() if v.published_at else None,
                "published_by": v.published_by,
                "created_at": v.created_at.isoformat() if v.created_at else None,
                "created_by": v.created_by,
            }
            for v in versions
        ]

    def create_template_version(
        self, template_id: str, body: CreateTemplateVersion, tenant_id: str,
    ) -> Dict[str, Any]:
        try:
            tid = uuid.UUID(template_id)
        except ValueError:
            raise ValidationError("Invalid template ID")
        template = self._db.query(DSRTemplateDB).filter(
            DSRTemplateDB.id == tid,
            DSRTemplateDB.tenant_id == uuid.UUID(tenant_id),
        ).first()
        if not template:
            raise NotFoundError("Template not found")
        version = DSRTemplateVersionDB(
            template_id=tid,
            version=body.version,
            subject=body.subject,
            body_html=body.body_html,
            body_text=body.body_text,
            status="draft",
        )
        self._db.add(version)
        self._db.commit()
        self._db.refresh(version)
        return {
            "id": str(version.id),
            "template_id": str(version.template_id),
            "version": version.version,
            "subject": version.subject,
            "body_html": version.body_html,
            "body_text": version.body_text,
            "status": version.status,
            "created_at": version.created_at.isoformat() if version.created_at else None,
        }

    def publish_template_version(self, version_id: str, tenant_id: str) -> Dict[str, Any]:
        try:
            vid = uuid.UUID(version_id)
        except ValueError:
            raise ValidationError("Invalid version ID")
        version = self._db.query(DSRTemplateVersionDB).filter(
            DSRTemplateVersionDB.id == vid,
        ).first()
        if not version:
            raise NotFoundError("Version not found")
        now = datetime.now(timezone.utc)
        version.status = "published"
        version.published_at = now
        version.published_by = "admin"
        self._db.commit()
        self._db.refresh(version)
        return {
            "id": str(version.id),
            "template_id": str(version.template_id),
            "version": version.version,
            "status": version.status,
            "published_at": version.published_at.isoformat(),
            "published_by": version.published_by,
        }
