# mypy: disable-error-code="arg-type,assignment,union-attr,no-any-return"
"""
Incident workflow service — risk assessment + Art. 33/34 notifications + measures.

Phase 1 Step 4: extracted from ``compliance.api.incident_routes``. CRUD +
stats + status + timeline + close live in
``compliance.services.incident_service``.
"""

import json
from datetime import datetime, timedelta, timezone
from typing import Any
from uuid import uuid4

from sqlalchemy import text
from sqlalchemy.orm import Session

from compliance.domain import NotFoundError, ValidationError
from compliance.schemas.incident import (
    AuthorityNotificationRequest,
    DataSubjectNotificationRequest,
    MeasureCreate,
    MeasureUpdate,
    RiskAssessmentRequest,
)
from compliance.services.incident_service import (
    _append_timeline,
    _calculate_72h_deadline,
    _calculate_risk_level,
    _is_notification_required,
    _measure_to_response,
    _parse_jsonb,
)


class IncidentWorkflowService:
    """Business logic for incident risk assessment, notifications, measures."""

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

    def _incident_row_or_raise(self, incident_id: str, columns: str) -> Any:
        row = (
            self.db.execute(
                text(f"SELECT {columns} FROM incident_incidents WHERE id = :id"),
                {"id": incident_id},
            )
            .mappings()
            .first()
        )
        if not row:
            raise NotFoundError("incident not found")
        return row

    # ------------------------------------------------------------------
    # Risk assessment
    # ------------------------------------------------------------------

    def assess_risk(
        self, incident_id: str, user_id: str, body: RiskAssessmentRequest
    ) -> dict[str, Any]:
        row = self._incident_row_or_raise(
            incident_id, "id, status, authority_notification"
        )

        risk_level = _calculate_risk_level(body.likelihood, body.impact)
        notification_required = _is_notification_required(risk_level)
        now = datetime.now(timezone.utc)

        assessment = {
            "likelihood": body.likelihood,
            "impact": body.impact,
            "risk_level": risk_level,
            "assessed_at": now.isoformat(),
            "assessed_by": user_id,
            "notes": body.notes or "",
        }

        new_status = "assessment"
        if notification_required:
            new_status = "notification_required"
            auth = _parse_jsonb(row["authority_notification"]) or {}
            auth["status"] = "pending"
            self.db.execute(
                text(
                    "UPDATE incident_incidents SET authority_notification = CAST(:an AS jsonb) "
                    "WHERE id = :id"
                ),
                {"id": incident_id, "an": json.dumps(auth)},
            )

        self.db.execute(
            text("""
                UPDATE incident_incidents
                SET risk_assessment = CAST(:ra AS jsonb),
                    status = :status,
                    updated_at = NOW()
                WHERE id = :id
            """),
            {"id": incident_id, "ra": json.dumps(assessment), "status": new_status},
        )
        _append_timeline(self.db, incident_id, {
            "timestamp": now.isoformat(),
            "action": "risk_assessed",
            "user_id": user_id,
            "details": f"Risk level: {risk_level} (likelihood={body.likelihood}, impact={body.impact})",
        })
        self.db.commit()

        return {
            "risk_assessment": assessment,
            "notification_required": notification_required,
            "incident_status": new_status,
        }

    # ------------------------------------------------------------------
    # Art. 33 authority notification
    # ------------------------------------------------------------------

    def notify_authority(
        self, incident_id: str, user_id: str, body: AuthorityNotificationRequest
    ) -> dict[str, Any]:
        row = self._incident_row_or_raise(
            incident_id, "id, detected_at, authority_notification"
        )

        now = datetime.now(timezone.utc)
        auth_existing = _parse_jsonb(row["authority_notification"]) or {}
        deadline_str = auth_existing.get("deadline")
        if not deadline_str and row["detected_at"]:
            detected = row["detected_at"]
            if hasattr(detected, "isoformat"):
                deadline_str = (detected + timedelta(hours=72)).isoformat()
            else:
                deadline_str = _calculate_72h_deadline(
                    datetime.fromisoformat(str(detected).replace("Z", "+00:00"))
                )

        notification = {
            "status": "sent",
            "deadline": deadline_str,
            "submitted_at": now.isoformat(),
            "authority_name": body.authority_name,
            "reference_number": body.reference_number or "",
            "contact_person": body.contact_person or "",
            "notes": body.notes or "",
        }

        self.db.execute(
            text("""
                UPDATE incident_incidents
                SET authority_notification = CAST(:an AS jsonb),
                    status = 'notification_sent',
                    updated_at = NOW()
                WHERE id = :id
            """),
            {"id": incident_id, "an": json.dumps(notification)},
        )
        _append_timeline(self.db, incident_id, {
            "timestamp": now.isoformat(),
            "action": "authority_notified",
            "user_id": user_id,
            "details": f"Authority notification submitted to {body.authority_name}",
        })
        self.db.commit()

        submitted_within_72h = True
        if deadline_str:
            try:
                deadline_dt = datetime.fromisoformat(deadline_str.replace("Z", "+00:00"))
                submitted_within_72h = now < deadline_dt
            except (ValueError, TypeError):
                pass

        return {
            "authority_notification": notification,
            "submitted_within_72h": submitted_within_72h,
        }

    # ------------------------------------------------------------------
    # Art. 34 data subject notification
    # ------------------------------------------------------------------

    def notify_subjects(
        self, incident_id: str, user_id: str, body: DataSubjectNotificationRequest
    ) -> dict[str, Any]:
        row = self._incident_row_or_raise(
            incident_id, "id, affected_data_subject_count"
        )

        now = datetime.now(timezone.utc)
        affected_count = body.affected_count or row["affected_data_subject_count"] or 0

        notification = {
            "required": True,
            "status": "sent",
            "sent_at": now.isoformat(),
            "affected_count": affected_count,
            "notification_text": body.notification_text,
            "channel": body.channel,
        }

        self.db.execute(
            text("""
                UPDATE incident_incidents
                SET data_subject_notification = CAST(:dsn AS jsonb),
                    updated_at = NOW()
                WHERE id = :id
            """),
            {"id": incident_id, "dsn": json.dumps(notification)},
        )
        _append_timeline(self.db, incident_id, {
            "timestamp": now.isoformat(),
            "action": "data_subjects_notified",
            "user_id": user_id,
            "details": f"Data subjects notified via {body.channel} ({affected_count} affected)",
        })
        self.db.commit()

        return {"data_subject_notification": notification}

    # ------------------------------------------------------------------
    # Measures
    # ------------------------------------------------------------------

    def add_measure(
        self, incident_id: str, user_id: str, body: MeasureCreate
    ) -> dict[str, Any]:
        self._incident_row_or_raise(incident_id, "id")
        measure_id = str(uuid4())
        now = datetime.now(timezone.utc)

        self.db.execute(
            text("""
                INSERT INTO incident_measures (
                    id, incident_id, title, description, measure_type, status,
                    responsible, due_date, created_at, updated_at
                ) VALUES (
                    :id, :incident_id, :title, :description, :measure_type, 'planned',
                    :responsible, :due_date, :now, :now
                )
            """),
            {
                "id": measure_id,
                "incident_id": incident_id,
                "title": body.title,
                "description": body.description or "",
                "measure_type": body.measure_type,
                "responsible": body.responsible or "",
                "due_date": body.due_date,
                "now": now.isoformat(),
            },
        )
        _append_timeline(self.db, incident_id, {
            "timestamp": now.isoformat(),
            "action": "measure_added",
            "user_id": user_id,
            "details": f"Measure added: {body.title} ({body.measure_type})",
        })
        self.db.commit()

        measure = (
            self.db.execute(
                text("SELECT * FROM incident_measures WHERE id = :id"),
                {"id": measure_id},
            )
            .mappings()
            .first()
        )
        return {"measure": _measure_to_response(measure)}

    def update_measure(
        self, measure_id: str, body: MeasureUpdate
    ) -> dict[str, Any]:
        check = self.db.execute(
            text("SELECT id FROM incident_measures WHERE id = :id"),
            {"id": measure_id},
        ).first()
        if not check:
            raise NotFoundError("measure not found")

        updates: list[str] = []
        params: dict[str, Any] = {"id": measure_id}
        for field in (
            "title", "description", "measure_type", "status", "responsible", "due_date",
        ):
            val = getattr(body, field, None)
            if val is not None:
                updates.append(f"{field} = :{field}")
                params[field] = val

        if not updates:
            raise ValidationError("no fields to update")

        updates.append("updated_at = NOW()")
        self.db.execute(
            text(f"UPDATE incident_measures SET {', '.join(updates)} WHERE id = :id"),
            params,
        )
        self.db.commit()

        measure = (
            self.db.execute(
                text("SELECT * FROM incident_measures WHERE id = :id"),
                {"id": measure_id},
            )
            .mappings()
            .first()
        )
        return {"measure": _measure_to_response(measure)}

    def complete_measure(self, measure_id: str) -> dict[str, Any]:
        check = self.db.execute(
            text("SELECT id FROM incident_measures WHERE id = :id"),
            {"id": measure_id},
        ).first()
        if not check:
            raise NotFoundError("measure not found")

        self.db.execute(
            text(
                "UPDATE incident_measures "
                "SET status = 'completed', completed_at = NOW(), updated_at = NOW() "
                "WHERE id = :id"
            ),
            {"id": measure_id},
        )
        self.db.commit()
        return {"message": "measure completed"}
