# 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"
"""
DSFA workflow service — status, section update, submit, approve, export, versions.

Phase 1 Step 4: extracted from ``compliance.api.dsfa_routes``. CRUD + helpers
live in ``compliance.services.dsfa_service``.
"""

import json
import logging
from datetime import datetime, timezone
from typing import Any, Optional

from sqlalchemy import text
from sqlalchemy.orm import Session

from compliance.api.versioning_utils import get_version, list_versions
from compliance.domain import NotFoundError, ValidationError
from compliance.schemas.dsfa import (
    DSFAApproveRequest,
    DSFASectionUpdate,
    DSFAStatusUpdate,
)
from compliance.services.dsfa_service import (
    VALID_STATUSES,
    _dsfa_to_response,
    _get_tenant_id,
    _log_audit,
)

logger = logging.getLogger(__name__)

SECTION_FIELD_MAP: dict[int, str] = {
    1: "processing_description",
    2: "necessity_assessment",
    3: "risk_assessment",
    4: "stakeholder_consultations",
    5: "measures",
    6: "dpo_opinion",
    7: "conclusion",
    8: "ai_use_case_modules",
}


class DSFAWorkflowService:
    """Status update, section update, submit, approve, export, versions."""

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

    # ------------------------------------------------------------------
    # Status update
    # ------------------------------------------------------------------

    def update_status(
        self,
        dsfa_id: str,
        tenant_id: Optional[str],
        body: DSFAStatusUpdate,
    ) -> dict[str, Any]:
        if body.status not in VALID_STATUSES:
            raise ValidationError(f"Ungültiger Status: {body.status}")

        tid = _get_tenant_id(tenant_id)
        existing = self.db.execute(
            text(
                "SELECT id, status FROM compliance_dsfas "
                "WHERE id = :id AND tenant_id = :tid"
            ),
            {"id": dsfa_id, "tid": tid},
        ).fetchone()
        if not existing:
            raise NotFoundError(f"DSFA {dsfa_id} nicht gefunden")

        params: dict[str, Any] = {
            "id": dsfa_id,
            "tid": tid,
            "status": body.status,
            "approved_at": (
                datetime.now(timezone.utc)
                if body.status == "approved"
                else None
            ),
            "approved_by": body.approved_by,
        }
        row = self.db.execute(
            text("""
                UPDATE compliance_dsfas
                SET status = :status, approved_at = :approved_at,
                    approved_by = :approved_by, updated_at = NOW()
                WHERE id = :id AND tenant_id = :tid
                RETURNING *
            """),
            params,
        ).fetchone()

        _log_audit(
            self.db, tid, dsfa_id, "STATUS_CHANGE",
            old_values={"status": existing["status"]},
            new_values={"status": body.status},
        )
        self.db.commit()
        return _dsfa_to_response(row)

    # ------------------------------------------------------------------
    # Section update
    # ------------------------------------------------------------------

    def update_section(
        self,
        dsfa_id: str,
        section_number: int,
        tenant_id: Optional[str],
        body: DSFASectionUpdate,
    ) -> dict[str, Any]:
        if section_number < 1 or section_number > 8:
            raise ValidationError(
                f"Section must be 1-8, got {section_number}"
            )

        tid = _get_tenant_id(tenant_id)
        existing = self.db.execute(
            text(
                "SELECT * FROM compliance_dsfas "
                "WHERE id = :id AND tenant_id = :tid"
            ),
            {"id": dsfa_id, "tid": tid},
        ).fetchone()
        if not existing:
            raise NotFoundError(f"DSFA {dsfa_id} nicht gefunden")

        field = SECTION_FIELD_MAP[section_number]
        jsonb_sections = {4, 5, 8}

        params: dict[str, Any] = {"id": dsfa_id, "tid": tid}

        if section_number in jsonb_sections:
            value = (
                body.extra
                if body.extra is not None
                else ([] if section_number != 4 else [])
            )
            params["val"] = json.dumps(value)
            set_clause = f"{field} = CAST(:val AS jsonb)"
        else:
            params["val"] = body.content or ""
            set_clause = f"{field} = :val"

        # Update section_progress
        progress = (
            existing["section_progress"]
            if existing["section_progress"]
            else {}
        )
        if isinstance(progress, str):
            progress = json.loads(progress)
        progress[f"section_{section_number}"] = True
        params["progress"] = json.dumps(progress)

        row = self.db.execute(
            text(f"""
                UPDATE compliance_dsfas
                SET {set_clause},
                    section_progress = CAST(:progress AS jsonb),
                    updated_at = NOW()
                WHERE id = :id AND tenant_id = :tid
                RETURNING *
            """),
            params,
        ).fetchone()

        _log_audit(
            self.db, tid, dsfa_id, "SECTION_UPDATE",
            new_values={"section": section_number, "field": field},
        )
        self.db.commit()
        return _dsfa_to_response(row)

    # ------------------------------------------------------------------
    # Submit for review
    # ------------------------------------------------------------------

    def submit_for_review(
        self, dsfa_id: str, tenant_id: Optional[str]
    ) -> dict[str, Any]:
        tid = _get_tenant_id(tenant_id)
        existing = self.db.execute(
            text(
                "SELECT id, status FROM compliance_dsfas "
                "WHERE id = :id AND tenant_id = :tid"
            ),
            {"id": dsfa_id, "tid": tid},
        ).fetchone()
        if not existing:
            raise NotFoundError(f"DSFA {dsfa_id} nicht gefunden")

        if existing["status"] not in ("draft", "needs-update"):
            raise ValidationError(
                f"Kann nur aus Status 'draft' oder 'needs-update' "
                f"eingereicht werden, aktuell: {existing['status']}"
            )

        row = self.db.execute(
            text("""
                UPDATE compliance_dsfas
                SET status = 'in-review',
                    submitted_for_review_at = NOW(),
                    updated_at = NOW()
                WHERE id = :id AND tenant_id = :tid
                RETURNING *
            """),
            {"id": dsfa_id, "tid": tid},
        ).fetchone()

        _log_audit(
            self.db, tid, dsfa_id, "SUBMIT_FOR_REVIEW",
            old_values={"status": existing["status"]},
            new_values={"status": "in-review"},
        )
        self.db.commit()
        return {
            "message": "DSFA zur Prüfung eingereicht",
            "status": "in-review",
            "dsfa": _dsfa_to_response(row),
        }

    # ------------------------------------------------------------------
    # Approve / reject
    # ------------------------------------------------------------------

    def approve(
        self,
        dsfa_id: str,
        tenant_id: Optional[str],
        body: DSFAApproveRequest,
    ) -> dict[str, Any]:
        tid = _get_tenant_id(tenant_id)
        existing = self.db.execute(
            text(
                "SELECT id, status FROM compliance_dsfas "
                "WHERE id = :id AND tenant_id = :tid"
            ),
            {"id": dsfa_id, "tid": tid},
        ).fetchone()
        if not existing:
            raise NotFoundError(f"DSFA {dsfa_id} nicht gefunden")

        if existing["status"] != "in-review":
            raise ValidationError(
                f"Nur DSFAs im Status 'in-review' können genehmigt werden, "
                f"aktuell: {existing['status']}"
            )

        if body.approved:
            new_status = "approved"
            self.db.execute(
                text("""
                    UPDATE compliance_dsfas
                    SET status = 'approved',
                        approved_by = :approved_by,
                        approved_at = NOW(),
                        updated_at = NOW()
                    WHERE id = :id AND tenant_id = :tid
                    RETURNING *
                """),
                {
                    "id": dsfa_id,
                    "tid": tid,
                    "approved_by": body.approved_by or "system",
                },
            ).fetchone()
        else:
            new_status = "needs-update"
            self.db.execute(
                text("""
                    UPDATE compliance_dsfas
                    SET status = 'needs-update', updated_at = NOW()
                    WHERE id = :id AND tenant_id = :tid
                    RETURNING *
                """),
                {"id": dsfa_id, "tid": tid},
            ).fetchone()

        _log_audit(
            self.db, tid, dsfa_id,
            "APPROVE" if body.approved else "REJECT",
            old_values={"status": existing["status"]},
            new_values={"status": new_status, "comments": body.comments},
        )
        self.db.commit()
        return {
            "message": (
                "DSFA genehmigt"
                if body.approved
                else "DSFA zurückgewiesen"
            ),
            "status": new_status,
        }

    # ------------------------------------------------------------------
    # Export JSON
    # ------------------------------------------------------------------

    def export_json(
        self, dsfa_id: str, tenant_id: Optional[str], fmt: str
    ) -> dict[str, Any]:
        tid = _get_tenant_id(tenant_id)
        row = self.db.execute(
            text(
                "SELECT * FROM compliance_dsfas "
                "WHERE id = :id AND tenant_id = :tid"
            ),
            {"id": dsfa_id, "tid": tid},
        ).fetchone()
        if not row:
            raise NotFoundError(f"DSFA {dsfa_id} nicht gefunden")

        dsfa_data = _dsfa_to_response(row)
        return {
            "exported_at": datetime.now(timezone.utc).isoformat(),
            "format": fmt,
            "dsfa": dsfa_data,
        }

    # ------------------------------------------------------------------
    # Versions
    # ------------------------------------------------------------------

    def list_versions(
        self, dsfa_id: str, tenant_id: Optional[str]
    ) -> Any:
        tid = _get_tenant_id(tenant_id)
        return list_versions(self.db, "dsfa", dsfa_id, tid)

    def get_version(
        self,
        dsfa_id: str,
        version_number: int,
        tenant_id: Optional[str],
    ) -> Any:
        tid = _get_tenant_id(tenant_id)
        v = get_version(self.db, "dsfa", dsfa_id, version_number, tid)
        if not v:
            raise NotFoundError(
                f"Version {version_number} not found"
            )
        return v
