# mypy: disable-error-code="arg-type,assignment,union-attr"
"""
Template Rule service — Empfehlungs-Regeln + Versionierung + Approval-Workflow
+ Tenant-Overrides.

Analog zu ``legal_document_service.py`` aufgebaut:
- ``_transition()``-Helper für Lifecycle-Übergänge
- Audit-Trail in ``compliance_template_rule_approvals``
- Service-Klasse mit DB-Session-Konstruktor
"""

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

from sqlalchemy.orm import Session

from compliance.db.template_rule_models import (
    TemplateRuleDB,
    TemplateRuleVersionDB,
    TemplateRuleApprovalDB,
    TenantRuleOverrideDB,
)
from compliance.domain import NotFoundError, ValidationError
from compliance.schemas.template_rule import (
    ApprovalActionRequest,
    ApprovalHistoryEntry,
    OverrideCreate,
    OverrideResponse,
    RejectActionRequest,
    RuleCondition,
    RuleCreate,
    RuleResponse,
    RuleVersionCreate,
    RuleVersionResponse,
    RuleVersionUpdate,
    SubmitForReviewRequest,
)


# ---------------------------------------------------------------- response mappers

def _rule_to_response(rule: TemplateRuleDB) -> RuleResponse:
    return RuleResponse(
        id=str(rule.id),
        rule_key=rule.rule_key,
        document_type=rule.document_type,
        title=rule.title,
        current_version_id=str(rule.current_version_id) if rule.current_version_id else None,
        created_at=rule.created_at,
        updated_at=rule.updated_at,
    )


def _version_to_response(v: TemplateRuleVersionDB) -> RuleVersionResponse:
    return RuleVersionResponse(
        id=str(v.id),
        rule_id=str(v.rule_id),
        version_number=v.version_number,
        status=v.status,
        is_live=bool(v.is_live),
        classification=v.classification,
        conditions=v.conditions or {},
        source_citation=v.source_citation or "",
        rationale=v.rationale,
        change_summary=v.change_summary,
        created_by=v.created_by,
        submitted_by=v.submitted_by,
        submitted_at=v.submitted_at,
        approved_by=v.approved_by,
        approved_at=v.approved_at,
        published_by=v.published_by,
        published_at=v.published_at,
        rejected_by=v.rejected_by,
        rejected_at=v.rejected_at,
        rejection_reason=v.rejection_reason,
        created_at=v.created_at,
        updated_at=v.updated_at,
    )


def _override_to_response(o: TenantRuleOverrideDB) -> OverrideResponse:
    return OverrideResponse(
        id=str(o.id),
        tenant_id=o.tenant_id,
        rule_id=str(o.rule_id),
        override_classification=o.override_classification,
        reason=o.reason,
        created_by=o.created_by,
        created_at=o.created_at,
        updated_at=o.updated_at,
    )


def _log_approval(
    db: Session,
    version_id: Any,
    action: str,
    approver: Optional[str] = None,
    comment: Optional[str] = None,
) -> TemplateRuleApprovalDB:
    entry = TemplateRuleApprovalDB(
        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,
) -> RuleVersionResponse:
    version = (
        db.query(TemplateRuleVersionDB)
        .filter(TemplateRuleVersionDB.id == version_id)
        .first()
    )
    if not version:
        raise NotFoundError(f"Rule 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, v in extra_updates.items():
            setattr(version, k, v)

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


# ---------------------------------------------------------------- service

class TemplateRuleService:
    """Business logic for rules, versions, approval workflow and tenant overrides."""

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

    # ----- Rules (Hülle) -----

    def list_rules(
        self,
        document_type: Optional[str] = None,
    ) -> list[RuleResponse]:
        q = self.db.query(TemplateRuleDB)
        if document_type:
            q = q.filter(TemplateRuleDB.document_type == document_type)
        rules = q.order_by(TemplateRuleDB.created_at.asc()).all()
        return [_rule_to_response(r) for r in rules]

    def get_rule(self, rule_id: str) -> RuleResponse:
        rule = self._rule_or_raise(rule_id)
        return _rule_to_response(rule)

    def _rule_or_raise(self, rule_id: str) -> TemplateRuleDB:
        rule = (
            self.db.query(TemplateRuleDB)
            .filter(TemplateRuleDB.id == rule_id)
            .first()
        )
        if not rule:
            raise NotFoundError(f"Rule {rule_id} not found")
        return rule

    def create_rule(self, request: RuleCreate) -> RuleResponse:
        existing = (
            self.db.query(TemplateRuleDB)
            .filter(TemplateRuleDB.rule_key == request.rule_key)
            .first()
        )
        if existing:
            raise ValidationError(f"Rule with key '{request.rule_key}' already exists")
        rule = TemplateRuleDB(
            rule_key=request.rule_key,
            document_type=request.document_type,
            title=request.title,
        )
        self.db.add(rule)
        self.db.commit()
        self.db.refresh(rule)
        return _rule_to_response(rule)

    def delete_rule(self, rule_id: str) -> None:
        rule = self._rule_or_raise(rule_id)
        self.db.delete(rule)
        self.db.commit()

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

    def list_versions_for(self, rule_id: str) -> list[RuleVersionResponse]:
        self._rule_or_raise(rule_id)
        versions = (
            self.db.query(TemplateRuleVersionDB)
            .filter(TemplateRuleVersionDB.rule_id == rule_id)
            .order_by(TemplateRuleVersionDB.version_number.desc())
            .all()
        )
        return [_version_to_response(v) for v in versions]

    def get_version(self, version_id: str) -> RuleVersionResponse:
        v = self._version_or_raise(version_id)
        return _version_to_response(v)

    def _version_or_raise(self, version_id: str) -> TemplateRuleVersionDB:
        v = (
            self.db.query(TemplateRuleVersionDB)
            .filter(TemplateRuleVersionDB.id == version_id)
            .first()
        )
        if not v:
            raise NotFoundError(f"Rule version {version_id} not found")
        return v

    def create_version(self, request: RuleVersionCreate) -> RuleVersionResponse:
        """Erzeugt einen neuen Draft. Vorhandener offener Draft blockiert."""
        self._rule_or_raise(request.rule_id)

        # Es darf nur eine nicht-finalisierte Version geben (draft|review)
        open_v = (
            self.db.query(TemplateRuleVersionDB)
            .filter(TemplateRuleVersionDB.rule_id == request.rule_id)
            .filter(TemplateRuleVersionDB.status.in_(["draft", "review"]))
            .first()
        )
        if open_v:
            raise ValidationError(
                f"Rule {request.rule_id} already has an open draft (version {open_v.version_number})"
            )

        # Nächste Versionsnummer ermitteln
        last = (
            self.db.query(TemplateRuleVersionDB)
            .filter(TemplateRuleVersionDB.rule_id == request.rule_id)
            .order_by(TemplateRuleVersionDB.version_number.desc())
            .first()
        )
        next_n = (last.version_number + 1) if last else 1

        if request.classification not in ("required", "recommended", "optional"):
            raise ValidationError(f"Invalid classification '{request.classification}'")

        if not request.source_citation or not request.source_citation.strip():
            raise ValidationError("source_citation is required (Pflichtfeld)")

        version = TemplateRuleVersionDB(
            rule_id=request.rule_id,
            version_number=next_n,
            status="draft",
            is_live=0,
            classification=request.classification,
            conditions=request.conditions.dict(),
            source_citation=request.source_citation.strip(),
            rationale=request.rationale,
            created_by=request.created_by,
        )
        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: RuleVersionUpdate
    ) -> RuleVersionResponse:
        v = self._version_or_raise(version_id)
        if v.status != "draft":
            raise ValidationError(
                f"Only draft versions can be edited (current: {v.status})"
            )

        if request.classification is not None:
            if request.classification not in ("required", "recommended", "optional"):
                raise ValidationError(f"Invalid classification '{request.classification}'")
            v.classification = request.classification
        if request.conditions is not None:
            v.conditions = request.conditions.dict()
        if request.source_citation is not None:
            v.source_citation = request.source_citation.strip()
        if request.rationale is not None:
            v.rationale = request.rationale
        if request.change_summary is not None:
            v.change_summary = request.change_summary

        v.updated_at = datetime.now(timezone.utc)
        self.db.commit()
        self.db.refresh(v)
        return _version_to_response(v)

    # ----- Lifecycle -----

    def submit_for_review(
        self, version_id: str, request: SubmitForReviewRequest
    ) -> RuleVersionResponse:
        if not request.change_summary or not request.change_summary.strip():
            raise ValidationError("change_summary is required when submitting for review")

        # change_summary erst persistieren
        v = self._version_or_raise(version_id)
        v.change_summary = request.change_summary.strip()
        self.db.flush()

        return _transition(
            db=self.db,
            version_id=version_id,
            from_statuses=["draft"],
            to_status="review",
            action="submitted",
            approver=request.submitter,
            comment=request.comment,
            extra_updates={
                "submitted_by": request.submitter,
                "submitted_at": datetime.now(timezone.utc),
            },
        )

    def approve(
        self, version_id: str, request: ApprovalActionRequest
    ) -> RuleVersionResponse:
        return _transition(
            db=self.db,
            version_id=version_id,
            from_statuses=["review"],
            to_status="approved",
            action="approved",
            approver=request.approver,
            comment=request.comment,
            extra_updates={
                "approved_by": request.approver,
                "approved_at": datetime.now(timezone.utc),
            },
        )

    def publish(
        self, version_id: str, request: ApprovalActionRequest
    ) -> RuleVersionResponse:
        # Auf 'approved' → publish: vorige published Version archivieren, aktuelle live machen
        v = self._version_or_raise(version_id)
        if v.status != "approved":
            raise ValidationError(
                f"Only approved versions can be published (current: {v.status})"
            )

        # Vorige live demoten
        prev_live = (
            self.db.query(TemplateRuleVersionDB)
            .filter(TemplateRuleVersionDB.rule_id == v.rule_id)
            .filter(TemplateRuleVersionDB.is_live == 1)
            .first()
        )
        if prev_live:
            prev_live.status = "archived"
            prev_live.is_live = 0
            prev_live.updated_at = datetime.now(timezone.utc)
            _log_approval(self.db, prev_live.id, action="archived", approver=request.approver)

        # Aktuelle live machen
        now = datetime.now(timezone.utc)
        v.status = "published"
        v.is_live = 1
        v.published_by = request.approver
        v.published_at = now
        v.updated_at = now

        # current_version_id auf rule setzen
        rule = self._rule_or_raise(str(v.rule_id))
        rule.current_version_id = v.id
        rule.updated_at = now

        _log_approval(self.db, v.id, action="published", approver=request.approver, comment=request.comment)
        self.db.commit()
        self.db.refresh(v)
        return _version_to_response(v)

    def reject(
        self, version_id: str, request: RejectActionRequest
    ) -> RuleVersionResponse:
        if not request.rejection_reason or not request.rejection_reason.strip():
            raise ValidationError("rejection_reason is required")

        return _transition(
            db=self.db,
            version_id=version_id,
            from_statuses=["review"],
            to_status="rejected",
            action="rejected",
            approver=request.rejector,
            comment=request.comment,
            extra_updates={
                "rejected_by": request.rejector,
                "rejected_at": datetime.now(timezone.utc),
                "rejection_reason": request.rejection_reason.strip(),
            },
        )

    def approval_history(self, version_id: str) -> list[ApprovalHistoryEntry]:
        self._version_or_raise(version_id)
        entries = (
            self.db.query(TemplateRuleApprovalDB)
            .filter(TemplateRuleApprovalDB.version_id == version_id)
            .order_by(TemplateRuleApprovalDB.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
        ]

    # ----- Tenant-Overrides -----

    def list_overrides(self, tenant_id: str) -> list[OverrideResponse]:
        os = (
            self.db.query(TenantRuleOverrideDB)
            .filter(TenantRuleOverrideDB.tenant_id == tenant_id)
            .order_by(TenantRuleOverrideDB.created_at.desc())
            .all()
        )
        return [_override_to_response(o) for o in os]

    def upsert_override(
        self, tenant_id: str, request: OverrideCreate
    ) -> OverrideResponse:
        self._rule_or_raise(request.rule_id)

        if request.override_classification is not None:
            if request.override_classification not in ("required", "recommended", "optional"):
                raise ValidationError(
                    f"Invalid override_classification '{request.override_classification}'"
                )
        if not request.reason or not request.reason.strip():
            raise ValidationError("reason is required")

        existing = (
            self.db.query(TenantRuleOverrideDB)
            .filter(TenantRuleOverrideDB.tenant_id == tenant_id)
            .filter(TenantRuleOverrideDB.rule_id == request.rule_id)
            .first()
        )
        if existing:
            existing.override_classification = request.override_classification
            existing.reason = request.reason.strip()
            existing.created_by = request.created_by
            existing.updated_at = datetime.now(timezone.utc)
            self.db.commit()
            self.db.refresh(existing)
            return _override_to_response(existing)

        override = TenantRuleOverrideDB(
            tenant_id=tenant_id,
            rule_id=request.rule_id,
            override_classification=request.override_classification,
            reason=request.reason.strip(),
            created_by=request.created_by,
        )
        self.db.add(override)
        self.db.commit()
        self.db.refresh(override)
        return _override_to_response(override)

    def delete_override(self, tenant_id: str, override_id: str) -> None:
        o = (
            self.db.query(TenantRuleOverrideDB)
            .filter(TenantRuleOverrideDB.id == override_id)
            .filter(TenantRuleOverrideDB.tenant_id == tenant_id)
            .first()
        )
        if not o:
            raise NotFoundError(f"Override {override_id} not found for tenant {tenant_id}")
        self.db.delete(o)
        self.db.commit()


__all__ = [
    "TemplateRuleService",
    "_rule_to_response",
    "_version_to_response",
    "_override_to_response",
    "_transition",
    "_log_approval",
]
