# mypy: disable-error-code="arg-type,assignment,union-attr"
"""
Legal Document service — documents + versions + approval workflow + public endpoints.

Phase 1 Step 4: extracted from ``compliance.api.legal_document_routes``.
Consents, audit log, and cookie categories live in
``compliance.services.legal_document_consent_service``.

Module-level helpers (_doc_to_response, _version_to_response, _transition,
_log_approval) are re-exported from the route module for legacy tests.
"""

import io
from datetime import datetime, timezone
from typing import Any, Optional

from sqlalchemy.orm import Session

from compliance.db.legal_document_models import (
    LegalDocumentApprovalDB,
    LegalDocumentDB,
    LegalDocumentVersionDB,
)
from compliance.domain import NotFoundError, ValidationError
from compliance.schemas.legal_document import (
    ActionRequest,
    ApprovalHistoryEntry,
    DocumentCreate,
    DocumentResponse,
    VersionCreate,
    VersionResponse,
    VersionUpdate,
)


def _doc_to_response(doc: LegalDocumentDB) -> DocumentResponse:
    return DocumentResponse(
        id=str(doc.id),
        tenant_id=doc.tenant_id,
        type=doc.type,
        name=doc.name,
        description=doc.description,
        mandatory=doc.mandatory or False,
        created_at=doc.created_at,
        updated_at=doc.updated_at,
    )


def _version_to_response(v: LegalDocumentVersionDB) -> VersionResponse:
    return VersionResponse(
        id=str(v.id),
        document_id=str(v.document_id),
        version=v.version,
        language=v.language or "de",
        title=v.title,
        content=v.content,
        summary=v.summary,
        status=v.status or "draft",
        created_by=v.created_by,
        approved_by=v.approved_by,
        approved_at=v.approved_at,
        submitted_by=getattr(v, "submitted_by", None),
        submitted_at=getattr(v, "submitted_at", None),
        approved_internal_by=getattr(v, "approved_internal_by", None),
        approved_internal_at=getattr(v, "approved_internal_at", None),
        approved_client_by=getattr(v, "approved_client_by", None),
        approved_client_at=getattr(v, "approved_client_at", None),
        rejection_reason=v.rejection_reason,
        created_at=v.created_at,
        updated_at=v.updated_at,
    )


def _log_approval(
    db: Session,
    version_id: Any,
    action: str,
    approver: Optional[str] = None,
    comment: Optional[str] = None,
) -> LegalDocumentApprovalDB:
    entry = LegalDocumentApprovalDB(
        version_id=version_id,
        action=action,
        approver=approver,
        comment=comment,
    )
    db.add(entry)
    return entry


def _transition(
    db: Session,
    version_id: str,
    from_statuses: list[str],
    to_status: str,
    action: str,
    approver: Optional[str],
    comment: Optional[str],
    extra_updates: Optional[dict[str, Any]] = None,
) -> VersionResponse:
    version = (
        db.query(LegalDocumentVersionDB)
        .filter(LegalDocumentVersionDB.id == version_id)
        .first()
    )
    if not version:
        raise NotFoundError(f"Version {version_id} not found")
    if version.status not in from_statuses:
        raise ValidationError(
            f"Cannot perform '{action}' on version with status "
            f"'{version.status}' (expected: {from_statuses})"
        )

    version.status = to_status
    version.updated_at = datetime.now(timezone.utc)
    if extra_updates:
        for k, val in extra_updates.items():
            setattr(version, k, val)

    _log_approval(db, version.id, action=action, approver=approver, comment=comment)
    db.commit()
    db.refresh(version)
    return _version_to_response(version)


class LegalDocumentService:
    """Business logic for legal documents, versions, and approval workflow."""

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

    # ------------------------------------------------------------------
    # Documents
    # ------------------------------------------------------------------

    def list_documents(
        self, tenant_id: Optional[str], type_filter: Optional[str]
    ) -> dict[str, Any]:
        q = self.db.query(LegalDocumentDB)
        if tenant_id:
            q = q.filter(LegalDocumentDB.tenant_id == tenant_id)
        if type_filter:
            q = q.filter(LegalDocumentDB.type == type_filter)
        docs = q.order_by(LegalDocumentDB.created_at.desc()).all()
        return {"documents": [_doc_to_response(d).dict() for d in docs]}

    def create_document(self, request: DocumentCreate) -> DocumentResponse:
        doc = LegalDocumentDB(
            tenant_id=request.tenant_id,
            type=request.type,
            name=request.name,
            description=request.description,
            mandatory=request.mandatory,
        )
        self.db.add(doc)
        self.db.commit()
        self.db.refresh(doc)
        return _doc_to_response(doc)

    def _doc_or_raise(self, document_id: str) -> LegalDocumentDB:
        doc = (
            self.db.query(LegalDocumentDB)
            .filter(LegalDocumentDB.id == document_id)
            .first()
        )
        if not doc:
            raise NotFoundError(f"Document {document_id} not found")
        return doc

    def get_document(self, document_id: str) -> DocumentResponse:
        return _doc_to_response(self._doc_or_raise(document_id))

    def delete_document(self, document_id: str) -> None:
        doc = self._doc_or_raise(document_id)
        self.db.delete(doc)
        self.db.commit()

    def list_documents_with_versions(
        self, tenant_id: Optional[str], type_filter: Optional[str]
    ) -> dict[str, Any]:
        """Liefert alle Docs + jeweils latest version (bevorzugt published, sonst neueste).

        Eine Roundtrip statt N+1, fuer die Document-Library-UI.
        """
        q = self.db.query(LegalDocumentDB)
        if tenant_id:
            q = q.filter(LegalDocumentDB.tenant_id == tenant_id)
        if type_filter:
            q = q.filter(LegalDocumentDB.type == type_filter)
        docs = q.order_by(LegalDocumentDB.created_at.desc()).all()

        out: list[dict[str, Any]] = []
        for doc in docs:
            published = (
                self.db.query(LegalDocumentVersionDB)
                .filter(
                    LegalDocumentVersionDB.document_id == doc.id,
                    LegalDocumentVersionDB.status == "published",
                )
                .order_by(LegalDocumentVersionDB.created_at.desc())
                .first()
            )
            latest = published or (
                self.db.query(LegalDocumentVersionDB)
                .filter(LegalDocumentVersionDB.document_id == doc.id)
                .order_by(LegalDocumentVersionDB.created_at.desc())
                .first()
            )
            entry = _doc_to_response(doc).dict()
            entry["latest_version"] = (
                _version_to_response(latest).dict() if latest else None
            )
            entry["published_version"] = (
                _version_to_response(published).dict() if published else None
            )
            out.append(entry)
        return {"documents": out}

    def list_versions_for(self, document_id: str) -> list[VersionResponse]:
        self._doc_or_raise(document_id)
        versions = (
            self.db.query(LegalDocumentVersionDB)
            .filter(LegalDocumentVersionDB.document_id == document_id)
            .order_by(LegalDocumentVersionDB.created_at.desc())
            .all()
        )
        return [_version_to_response(v) for v in versions]

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

    def create_version(self, request: VersionCreate) -> VersionResponse:
        doc = (
            self.db.query(LegalDocumentDB)
            .filter(LegalDocumentDB.id == request.document_id)
            .first()
        )
        if not doc:
            raise NotFoundError(f"Document {request.document_id} not found")

        version = LegalDocumentVersionDB(
            document_id=request.document_id,
            version=request.version,
            language=request.language,
            title=request.title,
            content=request.content,
            summary=request.summary,
            created_by=request.created_by,
            status="draft",
        )
        self.db.add(version)
        self.db.flush()

        _log_approval(self.db, version.id, action="created", approver=request.created_by)
        self.db.commit()
        self.db.refresh(version)
        return _version_to_response(version)

    def update_version(
        self, version_id: str, request: VersionUpdate
    ) -> VersionResponse:
        version = (
            self.db.query(LegalDocumentVersionDB)
            .filter(LegalDocumentVersionDB.id == version_id)
            .first()
        )
        if not version:
            raise NotFoundError(f"Version {version_id} not found")
        if version.status not in ("draft", "rejected"):
            raise ValidationError(
                f"Only draft/rejected versions can be edited (current: {version.status})"
            )

        for field, value in request.dict(exclude_none=True).items():
            setattr(version, field, value)
        version.updated_at = datetime.now(timezone.utc)
        self.db.commit()
        self.db.refresh(version)
        return _version_to_response(version)

    def get_version(self, version_id: str) -> VersionResponse:
        v = (
            self.db.query(LegalDocumentVersionDB)
            .filter(LegalDocumentVersionDB.id == version_id)
            .first()
        )
        if not v:
            raise NotFoundError(f"Version {version_id} not found")
        return _version_to_response(v)

    async def upload_word(self, filename: Optional[str], content_bytes: bytes) -> dict[str, Any]:
        if not filename or not filename.lower().endswith(".docx"):
            raise ValidationError("Only .docx files are supported")

        html_content = ""
        try:
            import mammoth  # noqa: F811
            result = mammoth.convert_to_html(io.BytesIO(content_bytes))
            html_content = result.value
        except ImportError:
            html_content = (
                f"<p>[DOCX-Import: {filename}]</p>"
                f"<p>Bitte installieren Sie 'mammoth' fuer DOCX-Konvertierung.</p>"
            )
        return {"html": html_content, "filename": filename}

    # ------------------------------------------------------------------
    # Workflow transitions (5-stage: draft → review_internal → review_client → approved → published)
    # ------------------------------------------------------------------

    def submit_internal_review(
        self, version_id: str, request: ActionRequest
    ) -> VersionResponse:
        """draft (oder rejected) → review_internal: DSB-Pruefung beginnt."""
        return _transition(
            self.db, version_id, ["draft", "rejected"], "review_internal", "submitted_internal",
            request.approver, request.comment,
            extra_updates={
                "submitted_by": request.approver,
                "submitted_at": datetime.now(timezone.utc),
            },
        )

    def approve_internal(
        self, version_id: str, request: ActionRequest
    ) -> VersionResponse:
        """review_internal → review_client: DSB hat geprueft, Mandant ist dran."""
        return _transition(
            self.db, version_id, ["review_internal"], "review_client", "approved_internal",
            request.approver, request.comment,
            extra_updates={
                "approved_internal_by": request.approver,
                "approved_internal_at": datetime.now(timezone.utc),
            },
        )

    def approve_client(
        self, version_id: str, request: ActionRequest
    ) -> VersionResponse:
        """review_client → approved: Mandant hat freigegeben."""
        now = datetime.now(timezone.utc)
        return _transition(
            self.db, version_id, ["review_client"], "approved", "approved_client",
            request.approver, request.comment,
            extra_updates={
                "approved_client_by": request.approver,
                "approved_client_at": now,
                # Legacy-Felder mitfuehren fuer alte Konsumenten
                "approved_by": request.approver,
                "approved_at": now,
            },
        )

    def reject(self, version_id: str, request: ActionRequest) -> VersionResponse:
        """Ablehnung aus review_internal oder review_client (oder legacy 'review') → rejected."""
        v = (
            self.db.query(LegalDocumentVersionDB)
            .filter(LegalDocumentVersionDB.id == version_id)
            .first()
        )
        action = "rejected"
        if v is not None:
            if v.status == "review_internal":
                action = "rejected_internal"
            elif v.status == "review_client":
                action = "rejected_client"
        return _transition(
            self.db, version_id,
            ["review", "review_internal", "review_client"],
            "rejected", action,
            request.approver, request.comment,
            extra_updates={"rejection_reason": request.comment},
        )

    def publish(self, version_id: str, request: ActionRequest) -> VersionResponse:
        """approved → published."""
        return _transition(
            self.db, version_id, ["approved"], "published", "published",
            request.approver, request.comment,
        )

    # ------------------------------------------------------------------
    # Backwards-compat aliases for 4-stage callers
    # ------------------------------------------------------------------

    def submit_review(self, version_id: str, request: ActionRequest) -> VersionResponse:
        """Backwards-compat alias — leitet auf submit_internal_review um."""
        return self.submit_internal_review(version_id, request)

    def approve(self, version_id: str, request: ActionRequest) -> VersionResponse:
        """Backwards-compat alias — leitet auf approve_internal (DSB-Schritt)."""
        return self.approve_internal(version_id, request)

    def approval_history(self, version_id: str) -> list[ApprovalHistoryEntry]:
        version = (
            self.db.query(LegalDocumentVersionDB)
            .filter(LegalDocumentVersionDB.id == version_id)
            .first()
        )
        if not version:
            raise NotFoundError(f"Version {version_id} not found")
        entries = (
            self.db.query(LegalDocumentApprovalDB)
            .filter(LegalDocumentApprovalDB.version_id == version_id)
            .order_by(LegalDocumentApprovalDB.created_at.asc())
            .all()
        )
        return [
            ApprovalHistoryEntry(
                id=str(e.id),
                version_id=str(e.version_id),
                action=e.action,
                approver=e.approver,
                comment=e.comment,
                created_at=e.created_at,
            )
            for e in entries
        ]

    # ------------------------------------------------------------------
    # Public endpoints (end-user facing)
    # ------------------------------------------------------------------

    def list_public(self, tenant_id: str) -> list[dict[str, Any]]:
        docs = (
            self.db.query(LegalDocumentDB)
            .filter(LegalDocumentDB.tenant_id == tenant_id)
            .order_by(LegalDocumentDB.created_at.desc())
            .all()
        )
        result: list[dict[str, Any]] = []
        for doc in docs:
            published = (
                self.db.query(LegalDocumentVersionDB)
                .filter(
                    LegalDocumentVersionDB.document_id == doc.id,
                    LegalDocumentVersionDB.status == "published",
                )
                .order_by(LegalDocumentVersionDB.created_at.desc())
                .first()
            )
            if published:
                result.append({
                    "id": str(doc.id),
                    "type": doc.type,
                    "name": doc.name,
                    "version": published.version,
                    "title": published.title,
                    "content": published.content,
                    "language": published.language,
                    "published_at": (
                        published.approved_at.isoformat() if published.approved_at else None
                    ),
                })
        return result

    def get_latest_published(
        self, tenant_id: str, document_type: str, language: str
    ) -> dict[str, Any]:
        doc = (
            self.db.query(LegalDocumentDB)
            .filter(
                LegalDocumentDB.tenant_id == tenant_id,
                LegalDocumentDB.type == document_type,
            )
            .first()
        )
        if not doc:
            raise NotFoundError(f"No document of type '{document_type}' found")

        version = (
            self.db.query(LegalDocumentVersionDB)
            .filter(
                LegalDocumentVersionDB.document_id == doc.id,
                LegalDocumentVersionDB.status == "published",
                LegalDocumentVersionDB.language == language,
            )
            .order_by(LegalDocumentVersionDB.created_at.desc())
            .first()
        )
        if not version:
            raise NotFoundError(
                f"No published version for type '{document_type}' in language '{language}'"
            )

        return {
            "document_id": str(doc.id),
            "type": doc.type,
            "name": doc.name,
            "version_id": str(version.id),
            "version": version.version,
            "title": version.title,
            "content": version.content,
            "language": version.language,
        }
