# 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 service — CRUD, stats, export, deadline processing.

Phase 1 Step 4: extracted from ``compliance.api.dsr_routes``. Workflow
actions (status changes, identity verification, assignment, completion,
rejection, communications, exception checks, templates) live in
``compliance.services.dsr_workflow_service``.

Helpers ``_dsr_to_dict``, ``_get_dsr_or_404``, ``_record_history``,
``_generate_request_number`` and constants are defined here and
re-exported from ``compliance.api.dsr_routes`` for legacy test imports.
"""

import csv
import io
import uuid
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, List, Optional

from sqlalchemy import func, or_, text
from sqlalchemy.orm import Session

from compliance.db.dsr_models import DSRRequestDB, DSRStatusHistoryDB
from compliance.domain import NotFoundError, ValidationError
from compliance.schemas.dsr import DSRCreate, DSRUpdate

# ============================================================================
# Constants
# ============================================================================

DEFAULT_TENANT = "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e"

ART17_EXCEPTIONS: List[Dict[str, str]] = [
    {
        "check_code": "art17_3_a",
        "article": "17(3)(a)",
        "label": "Meinungs- und Informationsfreiheit",
        "description": "Ausuebung des Rechts auf freie Meinungsaeusserung und Information",
    },
    {
        "check_code": "art17_3_b",
        "article": "17(3)(b)",
        "label": "Rechtliche Verpflichtung",
        "description": "Erfuellung einer rechtlichen Verpflichtung (z.B. Aufbewahrungspflichten)",
    },
    {
        "check_code": "art17_3_c",
        "article": "17(3)(c)",
        "label": "Oeffentliches Interesse",
        "description": "Gruende des oeffentlichen Interesses im Bereich Gesundheit",
    },
    {
        "check_code": "art17_3_d",
        "article": "17(3)(d)",
        "label": "Archivzwecke",
        "description": "Archivzwecke, wissenschaftliche/historische Forschung, Statistik",
    },
    {
        "check_code": "art17_3_e",
        "article": "17(3)(e)",
        "label": "Rechtsansprueche",
        "description": "Geltendmachung, Ausuebung oder Verteidigung von Rechtsanspruechen",
    },
]

VALID_REQUEST_TYPES = [
    "access", "rectification", "erasure", "restriction", "portability", "objection",
]
VALID_STATUSES = [
    "intake", "identity_verification", "processing", "completed", "rejected", "cancelled",
]
VALID_PRIORITIES = ["low", "normal", "high", "critical"]
VALID_SOURCES = ["web_form", "email", "letter", "phone", "in_person", "other"]

DEADLINE_DAYS: Dict[str, int] = {
    "access": 30,
    "rectification": 14,
    "erasure": 14,
    "restriction": 14,
    "portability": 30,
    "objection": 30,
}


# ============================================================================
# Module-level helpers (re-exported by compliance.api.dsr_routes)
# ============================================================================


def _generate_request_number(db: Session, tenant_id: str) -> str:
    """Generate next request number: DSR-YYYY-NNNNNN."""
    year = datetime.now(timezone.utc).year
    try:
        result = db.execute(text("SELECT nextval('compliance_dsr_request_number_seq')"))
        seq = result.scalar()
    except Exception:
        count = db.query(DSRRequestDB).count()
        seq = count + 1
    return f"DSR-{year}-{str(seq).zfill(6)}"


def _record_history(
    db: Session,
    dsr: DSRRequestDB,
    new_status: str,
    changed_by: str = "system",
    comment: Optional[str] = None,
) -> None:
    """Record status change in history."""
    entry = DSRStatusHistoryDB(
        tenant_id=dsr.tenant_id,
        dsr_id=dsr.id,
        previous_status=dsr.status,
        new_status=new_status,
        changed_by=changed_by,
        comment=comment,
    )
    db.add(entry)


def _dsr_to_dict(dsr: DSRRequestDB) -> Dict[str, Any]:
    """Convert DSR DB record to API response dict."""
    return {
        "id": str(dsr.id),
        "tenant_id": str(dsr.tenant_id),
        "request_number": dsr.request_number,
        "request_type": dsr.request_type,
        "status": dsr.status,
        "priority": dsr.priority,
        "requester_name": dsr.requester_name,
        "requester_email": dsr.requester_email,
        "requester_phone": dsr.requester_phone,
        "requester_address": dsr.requester_address,
        "requester_customer_id": dsr.requester_customer_id,
        "source": dsr.source,
        "source_details": dsr.source_details,
        "request_text": dsr.request_text,
        "notes": dsr.notes,
        "internal_notes": dsr.internal_notes,
        "received_at": dsr.received_at.isoformat() if dsr.received_at else None,
        "deadline_at": dsr.deadline_at.isoformat() if dsr.deadline_at else None,
        "extended_deadline_at": dsr.extended_deadline_at.isoformat() if dsr.extended_deadline_at else None,
        "extension_reason": dsr.extension_reason,
        "extension_approved_by": dsr.extension_approved_by,
        "extension_approved_at": dsr.extension_approved_at.isoformat() if dsr.extension_approved_at else None,
        "identity_verified": dsr.identity_verified,
        "verification_method": dsr.verification_method,
        "verified_at": dsr.verified_at.isoformat() if dsr.verified_at else None,
        "verified_by": dsr.verified_by,
        "verification_notes": dsr.verification_notes,
        "verification_document_ref": dsr.verification_document_ref,
        "assigned_to": dsr.assigned_to,
        "assigned_at": dsr.assigned_at.isoformat() if dsr.assigned_at else None,
        "assigned_by": dsr.assigned_by,
        "completed_at": dsr.completed_at.isoformat() if dsr.completed_at else None,
        "completion_notes": dsr.completion_notes,
        "rejection_reason": dsr.rejection_reason,
        "rejection_legal_basis": dsr.rejection_legal_basis,
        "erasure_checklist": dsr.erasure_checklist or [],
        "data_export": dsr.data_export or {},
        "rectification_details": dsr.rectification_details or {},
        "objection_details": dsr.objection_details or {},
        "affected_systems": dsr.affected_systems or [],
        "created_at": dsr.created_at.isoformat() if dsr.created_at else None,
        "updated_at": dsr.updated_at.isoformat() if dsr.updated_at else None,
        "created_by": dsr.created_by,
        "updated_by": dsr.updated_by,
    }


def _get_dsr_or_404(db: Session, dsr_id: str, tenant_id: str) -> DSRRequestDB:
    """Get DSR by ID or raise NotFoundError."""
    try:
        uid = uuid.UUID(dsr_id)
    except ValueError:
        raise ValidationError("Invalid DSR ID format")
    dsr = db.query(DSRRequestDB).filter(
        DSRRequestDB.id == uid,
        DSRRequestDB.tenant_id == uuid.UUID(tenant_id),
    ).first()
    if not dsr:
        raise NotFoundError("DSR not found")
    return dsr


# ============================================================================
# Service class
# ============================================================================


class DSRService:
    """CRUD, stats, export, and deadline processing for DSRs."""

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

    # -- Create --------------------------------------------------------------

    def create(self, body: DSRCreate, tenant_id: str) -> Dict[str, Any]:
        if body.request_type not in VALID_REQUEST_TYPES:
            raise ValidationError(f"Invalid request_type. Must be one of: {VALID_REQUEST_TYPES}")
        if body.source not in VALID_SOURCES:
            raise ValidationError(f"Invalid source. Must be one of: {VALID_SOURCES}")
        if body.priority and body.priority not in VALID_PRIORITIES:
            raise ValidationError(f"Invalid priority. Must be one of: {VALID_PRIORITIES}")

        now = datetime.now(timezone.utc)
        deadline_days = DEADLINE_DAYS.get(body.request_type, 30)
        request_number = _generate_request_number(self._db, tenant_id)

        dsr = DSRRequestDB(
            tenant_id=uuid.UUID(tenant_id),
            request_number=request_number,
            request_type=body.request_type,
            status="intake",
            priority=body.priority or "normal",
            requester_name=body.requester_name,
            requester_email=body.requester_email,
            requester_phone=body.requester_phone,
            requester_address=body.requester_address,
            requester_customer_id=body.requester_customer_id,
            source=body.source,
            source_details=body.source_details,
            request_text=body.request_text,
            notes=body.notes,
            received_at=now,
            deadline_at=now + timedelta(days=deadline_days),
            created_at=now,
            updated_at=now,
        )
        self._db.add(dsr)
        self._db.flush()

        history = DSRStatusHistoryDB(
            tenant_id=uuid.UUID(tenant_id),
            dsr_id=dsr.id,
            previous_status=None,
            new_status="intake",
            changed_by="system",
            comment="DSR erstellt",
        )
        self._db.add(history)
        self._db.commit()
        self._db.refresh(dsr)
        return _dsr_to_dict(dsr)

    # -- List ----------------------------------------------------------------

    def list(  # noqa: C901 — many filter params, straightforward
        self,
        tenant_id: str,
        *,
        status: Optional[str] = None,
        request_type: Optional[str] = None,
        assigned_to: Optional[str] = None,
        priority: Optional[str] = None,
        overdue_only: bool = False,
        search: Optional[str] = None,
        from_date: Optional[str] = None,
        to_date: Optional[str] = None,
        limit: int = 20,
        offset: int = 0,
    ) -> Dict[str, Any]:
        query = self._db.query(DSRRequestDB).filter(
            DSRRequestDB.tenant_id == uuid.UUID(tenant_id),
        )
        if status:
            query = query.filter(DSRRequestDB.status == status)
        if request_type:
            query = query.filter(DSRRequestDB.request_type == request_type)
        if assigned_to:
            query = query.filter(DSRRequestDB.assigned_to == assigned_to)
        if priority:
            query = query.filter(DSRRequestDB.priority == priority)
        if overdue_only:
            query = query.filter(
                DSRRequestDB.deadline_at < datetime.now(timezone.utc),
                DSRRequestDB.status.notin_(["completed", "rejected", "cancelled"]),
            )
        if search:
            search_term = f"%{search.lower()}%"
            query = query.filter(
                or_(
                    func.lower(func.coalesce(DSRRequestDB.requester_name, '')).like(search_term),
                    func.lower(func.coalesce(DSRRequestDB.requester_email, '')).like(search_term),
                    func.lower(func.coalesce(DSRRequestDB.request_number, '')).like(search_term),
                    func.lower(func.coalesce(DSRRequestDB.request_text, '')).like(search_term),
                )
            )
        if from_date:
            query = query.filter(DSRRequestDB.received_at >= from_date)
        if to_date:
            query = query.filter(DSRRequestDB.received_at <= to_date)

        total = query.count()
        dsrs = query.order_by(DSRRequestDB.created_at.desc()).offset(offset).limit(limit).all()
        return {
            "requests": [_dsr_to_dict(d) for d in dsrs],
            "total": total,
            "limit": limit,
            "offset": offset,
        }

    # -- Get -----------------------------------------------------------------

    def get(self, dsr_id: str, tenant_id: str) -> Dict[str, Any]:
        dsr = _get_dsr_or_404(self._db, dsr_id, tenant_id)
        return _dsr_to_dict(dsr)

    # -- Update --------------------------------------------------------------

    def update(self, dsr_id: str, body: DSRUpdate, tenant_id: str) -> Dict[str, Any]:
        dsr = _get_dsr_or_404(self._db, dsr_id, tenant_id)
        if body.priority is not None:
            if body.priority not in VALID_PRIORITIES:
                raise ValidationError(f"Invalid priority: {body.priority}")
            dsr.priority = body.priority
        if body.notes is not None:
            dsr.notes = body.notes
        if body.internal_notes is not None:
            dsr.internal_notes = body.internal_notes
        if body.assigned_to is not None:
            dsr.assigned_to = body.assigned_to
            dsr.assigned_at = datetime.now(timezone.utc)
        if body.request_text is not None:
            dsr.request_text = body.request_text
        if body.affected_systems is not None:
            dsr.affected_systems = body.affected_systems
        if body.erasure_checklist is not None:
            dsr.erasure_checklist = body.erasure_checklist
        if body.rectification_details is not None:
            dsr.rectification_details = body.rectification_details
        if body.objection_details is not None:
            dsr.objection_details = body.objection_details

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

    # -- Delete (soft) -------------------------------------------------------

    def delete(self, dsr_id: str, 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")
        _record_history(self._db, dsr, "cancelled", comment="DSR storniert")
        dsr.status = "cancelled"
        dsr.updated_at = datetime.now(timezone.utc)
        self._db.commit()
        return {"success": True, "message": "DSR cancelled"}

    # -- Stats ---------------------------------------------------------------

    def stats(self, tenant_id: str) -> Dict[str, Any]:
        tid = uuid.UUID(tenant_id)
        base = self._db.query(DSRRequestDB).filter(DSRRequestDB.tenant_id == tid)
        total = base.count()

        by_status = {s: base.filter(DSRRequestDB.status == s).count() for s in VALID_STATUSES}
        by_type = {t: base.filter(DSRRequestDB.request_type == t).count() for t in VALID_REQUEST_TYPES}

        now = datetime.now(timezone.utc)
        overdue = base.filter(
            DSRRequestDB.deadline_at < now,
            DSRRequestDB.status.notin_(["completed", "rejected", "cancelled"]),
        ).count()

        week_from_now = now + timedelta(days=7)
        due_this_week = base.filter(
            DSRRequestDB.deadline_at >= now,
            DSRRequestDB.deadline_at <= week_from_now,
            DSRRequestDB.status.notin_(["completed", "rejected", "cancelled"]),
        ).count()

        month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
        completed_this_month = base.filter(
            DSRRequestDB.status == "completed",
            DSRRequestDB.completed_at >= month_start,
        ).count()

        completed = base.filter(
            DSRRequestDB.status == "completed", DSRRequestDB.completed_at.isnot(None),
        ).all()
        if completed:
            total_days = sum(
                (d.completed_at - d.received_at).days
                for d in completed if d.completed_at and d.received_at
            )
            avg_days = total_days / len(completed)
        else:
            avg_days = 0

        return {
            "total": total,
            "by_status": by_status,
            "by_type": by_type,
            "overdue": overdue,
            "due_this_week": due_this_week,
            "average_processing_days": round(avg_days, 1),
            "completed_this_month": completed_this_month,
        }

    # -- Export --------------------------------------------------------------

    def export(self, tenant_id: str, fmt: str = "csv") -> Any:
        tid = uuid.UUID(tenant_id)
        dsrs = self._db.query(DSRRequestDB).filter(
            DSRRequestDB.tenant_id == tid,
        ).order_by(DSRRequestDB.created_at.desc()).all()

        if fmt == "json":
            return {
                "exported_at": datetime.now(timezone.utc).isoformat(),
                "total": len(dsrs),
                "requests": [_dsr_to_dict(d) for d in dsrs],
            }

        output = io.StringIO()
        writer = csv.writer(output, delimiter=';', quoting=csv.QUOTE_MINIMAL)
        writer.writerow([
            "ID", "Referenznummer", "Typ", "Name", "E-Mail", "Status",
            "Prioritaet", "Eingegangen", "Frist", "Abgeschlossen", "Quelle", "Zugewiesen",
        ])
        for dsr in dsrs:
            writer.writerow([
                str(dsr.id),
                dsr.request_number or "",
                dsr.request_type or "",
                dsr.requester_name or "",
                dsr.requester_email or "",
                dsr.status or "",
                dsr.priority or "",
                dsr.received_at.strftime("%Y-%m-%d") if dsr.received_at else "",
                dsr.deadline_at.strftime("%Y-%m-%d") if dsr.deadline_at else "",
                dsr.completed_at.strftime("%Y-%m-%d") if dsr.completed_at else "",
                dsr.source or "",
                dsr.assigned_to or "",
            ])
        output.seek(0)
        return output

    # -- Deadline processing -------------------------------------------------

    def process_deadlines(self, tenant_id: str) -> Dict[str, Any]:
        now = datetime.now(timezone.utc)
        tid = uuid.UUID(tenant_id)
        from sqlalchemy import and_
        overdue = self._db.query(DSRRequestDB).filter(
            DSRRequestDB.tenant_id == tid,
            DSRRequestDB.status.notin_(["completed", "rejected", "cancelled"]),
            or_(
                and_(DSRRequestDB.extended_deadline_at.isnot(None), DSRRequestDB.extended_deadline_at < now),
                and_(DSRRequestDB.extended_deadline_at.is_(None), DSRRequestDB.deadline_at < now),
            ),
        ).all()

        processed = []
        for dsr in overdue:
            processed.append({
                "id": str(dsr.id),
                "request_number": dsr.request_number,
                "status": dsr.status,
                "deadline_at": dsr.deadline_at.isoformat() if dsr.deadline_at else None,
                "extended_deadline_at": dsr.extended_deadline_at.isoformat() if dsr.extended_deadline_at else None,
                "days_overdue": (now - (dsr.extended_deadline_at or dsr.deadline_at)).days,
            })
        return {"processed": len(processed), "overdue_requests": processed}
