from __future__ import annotations

from datetime import UTC, datetime, timedelta

from sqlalchemy import func, or_, select
from sqlalchemy.orm import selectinload

from app.models.agent_run import AgentRun
from app.models.approval import ApprovalRecord
from app.models.employee import Employee
from app.models.employee_behavior_profile import EmployeeBehaviorProfileSnapshot
from app.models.financial_record import ExpenseClaim

PROFILE_TYPES_FOR_APPROVAL = {"expense", "process_quality"}
ATTENTION_LEVELS = {"watch", "review", "escalation"}
PENDING_CLAIM_STATUSES = {
    "submitted",
    "review",
    "in_progress",
    "pending",
    "pending_review",
}


class EmployeeBehaviorProfileStorageMixin:
    """所有画像数据入口都在首个 SQL 收窄到可信租户。"""

    def _resolve_employee_by_identifier(self, identifier: str) -> Employee | None:
        normalized = str(identifier or "").strip()
        if not normalized:
            return None
        normalized_email = normalized.lower()
        conditions = [
            Employee.id == normalized,
            Employee.name == normalized,
            Employee.employee_no == normalized,
        ]
        if "@" in normalized_email:
            conditions.append(func.lower(Employee.email) == normalized_email)
        return self.db.scalars(
            select(Employee)
            .where(
                Employee.tenant_id == self.tenant_id,
                or_(*conditions),
            )
            .order_by(Employee.created_at.asc())
            .limit(1)
        ).first()

    def _load_latest_snapshots(
        self,
        *,
        employee_id: str,
        window_days: int,
        expense_type_scope: str,
        scene: str,
    ) -> list[EmployeeBehaviorProfileSnapshot]:
        allowed_types = PROFILE_TYPES_FOR_APPROVAL if scene == "approval" else None
        rows = self._query_latest_rows(
            employee_id=employee_id,
            window_days=window_days,
            expense_type_scope=expense_type_scope,
            allowed_types=allowed_types,
        )
        if rows or expense_type_scope == "overall":
            return rows
        return self._query_latest_rows(
            employee_id=employee_id,
            window_days=window_days,
            expense_type_scope="overall",
            allowed_types=allowed_types,
        )

    def _query_latest_rows(
        self,
        *,
        employee_id: str,
        window_days: int,
        expense_type_scope: str,
        allowed_types: set[str] | None,
    ) -> list[EmployeeBehaviorProfileSnapshot]:
        stmt = select(EmployeeBehaviorProfileSnapshot).where(
            EmployeeBehaviorProfileSnapshot.tenant_id == self.tenant_id,
            EmployeeBehaviorProfileSnapshot.subject_id == employee_id,
            EmployeeBehaviorProfileSnapshot.window_days == window_days,
            EmployeeBehaviorProfileSnapshot.expense_type_scope == expense_type_scope,
        )
        if allowed_types:
            stmt = stmt.where(EmployeeBehaviorProfileSnapshot.profile_type.in_(allowed_types))
        rows = list(
            self.db.scalars(
                stmt.order_by(EmployeeBehaviorProfileSnapshot.calculated_at.desc())
            ).all()
        )
        latest_by_type: dict[str, EmployeeBehaviorProfileSnapshot] = {}
        for row in rows:
            latest_by_type.setdefault(row.profile_type, row)
        return list(latest_by_type.values())

    def _claim_belongs_to_employee(self, claim_id: str, employee_id: str) -> bool:
        if not str(claim_id or "").strip():
            return True
        return bool(
            self.db.scalar(
                select(ExpenseClaim.id).where(
                    ExpenseClaim.tenant_id == self.tenant_id,
                    ExpenseClaim.id == str(claim_id).strip(),
                    ExpenseClaim.employee_id == str(employee_id).strip(),
                )
            )
        )

    def _resolve_target_employee_ids(self, *, limit: int) -> list[str]:
        cutoff = datetime.now(UTC) - timedelta(days=180)
        claim_stmt = select(ExpenseClaim.employee_id).where(
            ExpenseClaim.tenant_id == self.tenant_id,
            ExpenseClaim.employee_id.is_not(None),
            or_(
                ExpenseClaim.occurred_at >= cutoff,
                ExpenseClaim.status.in_(PENDING_CLAIM_STATUSES),
            ),
        )
        snapshot_stmt = select(EmployeeBehaviorProfileSnapshot.subject_id).where(
            EmployeeBehaviorProfileSnapshot.tenant_id == self.tenant_id,
            EmployeeBehaviorProfileSnapshot.profile_level.in_(ATTENTION_LEVELS),
        )
        ordered: list[str] = []
        values = [
            *self.db.scalars(claim_stmt).all(),
            *self.db.scalars(snapshot_stmt).all(),
        ]
        for value in values:
            employee_id = str(value or "").strip()
            if employee_id and employee_id not in ordered:
                ordered.append(employee_id)
            if len(ordered) >= limit:
                break
        return ordered

    def _fetch_claims_since(self, cutoff: datetime) -> list[ExpenseClaim]:
        stmt = (
            select(ExpenseClaim)
            .options(
                selectinload(ExpenseClaim.items),
                selectinload(ExpenseClaim.employee),
            )
            .where(
                ExpenseClaim.tenant_id == self.tenant_id,
                ExpenseClaim.occurred_at >= cutoff,
            )
        )
        return list(self.db.scalars(stmt).all())

    def _fetch_agent_runs(
        self,
        identifiers: set[str],
        cutoff: datetime,
    ) -> list[AgentRun]:
        if not identifiers:
            return []
        stmt = (
            select(AgentRun)
            .options(selectinload(AgentRun.tool_calls))
            .where(
                AgentRun.route_json["tenant_id"].as_string() == self.tenant_id,
                AgentRun.ontology_json["tenant_id"].as_string() == self.tenant_id,
                AgentRun.started_at >= cutoff,
                AgentRun.user_id.in_(identifiers),
            )
        )
        return list(self.db.scalars(stmt).all())

    def _fetch_approval_records(
        self,
        employee_id: str,
        cutoff: datetime,
    ) -> list[ApprovalRecord]:
        stmt = (
            select(ApprovalRecord)
            .join(Employee, Employee.id == ApprovalRecord.approver_id)
            .where(
                Employee.tenant_id == self.tenant_id,
                ApprovalRecord.approver_id == employee_id,
                ApprovalRecord.created_at >= cutoff,
            )
        )
        return list(self.db.scalars(stmt).all())
