from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass
from datetime import UTC, datetime

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.api.deps import CurrentUserContext
from app.models.ai_memory import MemoryEntry
from app.schemas.expense_application_memory import (
    ExpenseApplicationMemoryApplication,
    ExpenseApplicationMemoryConflict,
)
from app.services.expense_cases import ExpenseCaseService

MEMORY_SCENE = "travel_application"
MEMORY_FIELD_KEY = "transport_mode"
SUPPORTED_TRANSPORT_VALUES = {"飞机", "火车", "轮船"}
SCOPE_PRIORITIES = {"enterprise": 300, "department": 200, "user": 100}

RefreshUserEntry = Callable[..., None]


@dataclass(frozen=True, slots=True)
class ExpenseApplicationMemoryResolution:
    winner: MemoryEntry | None
    value: str
    conflicts: tuple[ExpenseApplicationMemoryConflict, ...]
    effective_confidence: float
    status: str = "applied"

    def to_application(
        self,
        current_user: CurrentUserContext,
    ) -> ExpenseApplicationMemoryApplication:
        if self.winner is None:
            first = self.conflicts[0]
            return ExpenseApplicationMemoryApplication(
                memory_id="",
                value="",
                source="memory_conflict",
                status="conflict",
                confidence=0.0,
                effective_confidence=0.0,
                scope_type=first.scope_type,
                scope_id="",
                scope_label=first.scope_label,
                priority=first.priority,
                conflicts=list(self.conflicts),
                can_revoke=False,
                message="发现同一层级存在相互冲突的有效记忆，本次未自动填充，请联系管理员处理。",
            )

        entry = self.winner
        scope_type = _scope_type(entry)
        return ExpenseApplicationMemoryApplication(
            memory_id=entry.id,
            value=self.value,
            source=memory_source(scope_type),
            status=self.status,
            evidence_count=int(entry.evidence_count or 0),
            approved_evidence_count=int(entry.approved_evidence_count or 0),
            confidence=float(entry.confidence or 0),
            effective_confidence=self.effective_confidence,
            expires_at=entry.active_expires_at,
            scope_type=scope_type,
            scope_id=str(entry.scope_id or ""),
            scope_label=memory_scope_label(entry, current_user),
            priority=SCOPE_PRIORITIES[scope_type],
            conflicts=list(self.conflicts),
            can_revoke=scope_type == "user",
            message=_application_message(scope_type),
        )


class ExpenseApplicationMemoryResolver:
    """解析当前用户可见的企业、部门、个人记忆，并保守处理冲突。"""

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

    def resolve(
        self,
        current_user: CurrentUserContext,
        *,
        user_scope_id: str,
        refresh_user_entry: RefreshUserEntry,
    ) -> ExpenseApplicationMemoryResolution | None:
        tenant_id = ExpenseCaseService.normalize_tenant_id(current_user.tenant_id)
        relevant_scopes = [("enterprise", tenant_id)]
        department_id = str(getattr(current_user, "department_id", "") or "").strip()
        if department_id:
            relevant_scopes.append(("department", department_id))
        relevant_scopes.append(("user", user_scope_id))

        now = datetime.now(UTC)
        entries: list[MemoryEntry] = []
        for scope_type, scope_id in relevant_scopes:
            entries.extend(
                self.db.scalars(
                    select(MemoryEntry)
                    .where(
                        MemoryEntry.tenant_id == tenant_id,
                        MemoryEntry.scope_type == scope_type,
                        MemoryEntry.scope_id == scope_id,
                        MemoryEntry.scene == MEMORY_SCENE,
                        MemoryEntry.field_key == MEMORY_FIELD_KEY,
                        MemoryEntry.status.in_(["candidate", "active"]),
                    )
                    .order_by(
                        MemoryEntry.generation.desc(),
                        MemoryEntry.last_evidence_at.desc(),
                    )
                    .with_for_update()
                ).all()
            )

        user_entries = [entry for entry in entries if _scope_type(entry) == "user"]
        active_user_found = False
        for entry in user_entries:
            if entry.status != "active":
                continue
            refresh_user_entry(entry, now=now)
            active_user_found = active_user_found or entry.status == "active"
        for entry in user_entries:
            if entry.status != "candidate":
                continue
            refresh_user_entry(
                entry,
                now=now,
                allow_activation=not active_user_found,
            )
            active_user_found = active_user_found or entry.status == "active"

        eligible: list[MemoryEntry] = []
        for entry in entries:
            if (
                _scope_type(entry) != "user"
                and entry.status == "active"
                and _is_expired(entry, now)
            ):
                entry.status = "expired"
                entry.expired_at = now
            if entry.status != "active" or _is_expired(entry, now):
                continue
            if _entry_value(entry):
                eligible.append(entry)

        if not eligible:
            return None

        highest_priority = max(SCOPE_PRIORITIES[_scope_type(entry)] for entry in eligible)
        highest = [
            entry
            for entry in eligible
            if SCOPE_PRIORITIES[_scope_type(entry)] == highest_priority
        ]
        top_values = {_entry_value(entry) for entry in highest}
        if len(top_values) > 1:
            conflicts = tuple(
                _conflict(entry, current_user, reason="same_priority_conflict")
                for entry in highest
            )
            return ExpenseApplicationMemoryResolution(
                winner=None,
                value="",
                conflicts=conflicts,
                effective_confidence=0.0,
                status="conflict",
            )

        winner = highest[0]
        value = _entry_value(winner)
        conflicts = tuple(
            _conflict(entry, current_user, reason="lower_priority_overridden")
            for entry in eligible
            if SCOPE_PRIORITIES[_scope_type(entry)] < highest_priority
            and _entry_value(entry) != value
        )
        return ExpenseApplicationMemoryResolution(
            winner=winner,
            value=value,
            conflicts=conflicts,
            effective_confidence=_effective_confidence(winner, now),
        )


def _entry_value(entry: MemoryEntry) -> str:
    value_json = entry.value_json if isinstance(entry.value_json, dict) else {}
    value = str(value_json.get("value") or "").strip()
    return value if value in SUPPORTED_TRANSPORT_VALUES else ""


def _scope_type(entry: MemoryEntry) -> str:
    value = str(entry.scope_type or "user")
    return value if value in SCOPE_PRIORITIES else "user"


def memory_source(scope_type: str) -> str:
    return {
        "enterprise": "enterprise_policy_memory",
        "department": "department_policy_memory",
        "user": "verified_user_history",
    }[scope_type]


def memory_scope_label(entry: MemoryEntry, current_user: CurrentUserContext) -> str:
    scope_type = _scope_type(entry)
    if scope_type == "enterprise":
        return "企业统一规则"
    if scope_type == "department":
        department_name = str(current_user.department_name or "").strip()
        return f"部门规则（{department_name}）" if department_name else "部门规则"
    return "个人偏好"


def _application_message(scope_type: str) -> str:
    return {
        "enterprise": "已按企业统一规则预填出行方式，可在规则允许范围内调整。",
        "department": "已按当前部门规则预填出行方式，可继续修改。",
        "user": "已按可信历史记忆预填常用出行方式，可继续修改。",
    }[scope_type]


def _conflict(
    entry: MemoryEntry,
    current_user: CurrentUserContext,
    *,
    reason: str,
) -> ExpenseApplicationMemoryConflict:
    scope_type = _scope_type(entry)
    return ExpenseApplicationMemoryConflict(
        scope_type=scope_type,
        scope_label=memory_scope_label(entry, current_user),
        priority=SCOPE_PRIORITIES[scope_type],
        reason=reason,
    )


def _effective_confidence(entry: MemoryEntry, now: datetime) -> float:
    base = max(0.0, min(1.0, float(entry.confidence or 0)))
    if base == 0.0 and str(getattr(entry, "origin_type", "learned")) == "admin_managed":
        base = 1.0
    started_at = entry.activated_at or entry.created_at
    expires_at = entry.active_expires_at
    if started_at is None or expires_at is None:
        return round(base, 4)
    started = _aware(started_at)
    expires = _aware(expires_at)
    total_seconds = max(1.0, (expires - started).total_seconds())
    remaining_ratio = max(0.0, min(1.0, (expires - now).total_seconds() / total_seconds))
    # 有效期内仅降低解释性置信度，不会因衰减而提前停止应用。
    return round(base * max(0.5, remaining_ratio), 4)


def _is_expired(entry: MemoryEntry, now: datetime) -> bool:
    expires_at = entry.active_expires_at if entry.status == "active" else entry.candidate_expires_at
    return expires_at is not None and _aware(expires_at) <= now


def _aware(value: datetime) -> datetime:
    return value if value.tzinfo is not None else value.replace(tzinfo=UTC)
