from __future__ import annotations

from collections.abc import Iterable
from typing import Any

from app.api.deps import CurrentUserContext
from app.models.savings import (
    SavingsEvent,
    SavingsEvidenceLink,
    SavingsOpportunity,
    SavingsRealization,
)
from app.schemas.savings import (
    SavingsEventRead,
    SavingsEvidenceLinkRead,
    SavingsOpportunityRead,
    SavingsRealizationRead,
)
from app.services.savings_access_policy import SavingsAccessPolicy


class SavingsReadProjection:
    """把账本实体投影为字段白名单 DTO，并在最后一步附加可用动作。"""

    def opportunity_read(
        self,
        opportunity: SavingsOpportunity,
        current_user: CurrentUserContext,
        *,
        include_details: bool = True,
    ) -> SavingsOpportunityRead:
        permission = SavingsAccessPolicy.opportunity_permission(opportunity, current_user)
        if not permission.can_read:
            raise PermissionError(permission.read_only_reason or "当前用户无权读取该节省机会。")

        data = self._column_values(opportunity, SavingsOpportunityRead)
        data.update(
            available_actions=list(permission.available_actions),
            read_only_reason=permission.read_only_reason,
        )
        if include_details:
            data.update(
                baseline=getattr(opportunity, "baseline_snapshot", None),
                realizations=self.realization_list_read(
                    list(getattr(opportunity, "realizations", ()) or ()),
                    opportunity,
                    current_user,
                ),
                evidence=self.evidence_list_read(
                    list(getattr(opportunity, "evidence_links", ()) or ())
                ),
                events=self.event_list_read(list(getattr(opportunity, "events", ()) or ())),
            )
        else:
            data.update(baseline=None, realizations=[], evidence=[], events=[])
        return SavingsOpportunityRead.model_validate(data)

    def opportunity_snapshot(
        self,
        opportunity: SavingsOpportunity,
        current_user: CurrentUserContext,
    ) -> SavingsOpportunityRead:
        return self.opportunity_read(opportunity, current_user, include_details=False)

    def realization_read(
        self,
        realization: SavingsRealization,
        opportunity: SavingsOpportunity,
        current_user: CurrentUserContext,
    ) -> SavingsRealizationRead:
        permission = SavingsAccessPolicy.realization_permission(
            realization,
            opportunity,
            current_user,
        )
        if not permission.can_read:
            raise PermissionError(permission.read_only_reason or "当前用户无权读取该实际结果。")
        data = self._column_values(realization, SavingsRealizationRead)
        data.update(
            available_actions=list(permission.available_actions),
            read_only_reason=permission.read_only_reason,
        )
        return SavingsRealizationRead.model_validate(data)

    def realization_list_read(
        self,
        realizations: Iterable[SavingsRealization],
        opportunity: SavingsOpportunity,
        current_user: CurrentUserContext,
    ) -> list[SavingsRealizationRead]:
        return [
            self.realization_read(realization, opportunity, current_user)
            for realization in realizations
        ]

    @staticmethod
    def evidence_list_read(
        evidence: Iterable[SavingsEvidenceLink],
    ) -> list[SavingsEvidenceLinkRead]:
        return [SavingsEvidenceLinkRead.model_validate(item) for item in evidence]

    @staticmethod
    def event_list_read(events: Iterable[SavingsEvent]) -> list[SavingsEventRead]:
        return [SavingsEventRead.model_validate(item) for item in events]

    @staticmethod
    def event_read(event: SavingsEvent) -> SavingsEventRead:
        return SavingsEventRead.model_validate(event)

    @staticmethod
    def _column_values(instance: Any, schema_type: type[Any]) -> dict[str, Any]:
        excluded = {
            "available_actions",
            "read_only_reason",
            "baseline",
            "realizations",
            "evidence",
            "events",
        }
        return {
            field_name: getattr(instance, field_name)
            for field_name in schema_type.model_fields
            if field_name not in excluded and hasattr(instance, field_name)
        }
