from __future__ import annotations

import uuid

from .llm import LLMError, LLMService
from .schemas import (
    ModelRecommendationSet,
    PlanRecommendationRequest,
    PlanningMode,
    RecommendationItem,
    RecommendationResponse,
)


class RecommendationService:
    def __init__(self, llm: LLMService) -> None:
        self.llm = llm

    async def create(
        self, request: PlanRecommendationRequest
    ) -> RecommendationResponse:
        generated = await self.llm.recommend(request)
        recommendations = self._canonicalize(request, generated)
        message = (
            "已保留全部自选点位并给出建议顺序；最终路线与时长由客户端核算。"
            if request.mode == PlanningMode.CUSTOM
            else "已从当前 POI 白名单给出推荐；最终路线与时长由客户端核算。"
        )
        return RecommendationResponse(
            request_id=str(uuid.uuid4()),
            mode=request.mode,
            assistant_message=message,
            recommendations=recommendations,
            generation_mode=self.llm.mode,
        )

    @staticmethod
    def _canonicalize(
        request: PlanRecommendationRequest,
        generated: ModelRecommendationSet,
    ) -> list[RecommendationItem]:
        allowed_ids = {candidate.id for candidate in request.candidates}
        generated_by_id = {}
        for item in generated.recommendations:
            if item.poi_id not in allowed_ids:
                raise LLMError("模型返回了 POI 白名单外的 ID")
            if item.poi_id in generated_by_id:
                raise LLMError("模型返回了重复 POI ID")
            generated_by_id[item.poi_id] = item

        if request.mode == PlanningMode.CUSTOM:
            selected_ids = set(request.selected_poi_ids)
            ordered_ids = [
                item.poi_id
                for item in sorted(
                    generated.recommendations,
                    key=lambda item: item.order or 9,
                )
                if item.poi_id in selected_ids
            ]
            # Selection belongs to the user, not the model. Keep all selected IDs
            # even if a real model omits one, and discard unselected suggestions.
            ordered_ids.extend(
                poi_id
                for poi_id in request.selected_poi_ids
                if poi_id not in ordered_ids
            )
        else:
            ordered_ids = [
                item.poi_id
                for item in sorted(
                    generated.recommendations,
                    key=lambda item: item.order or 9,
                )
            ][:8]

        if not ordered_ids:
            raise LLMError("模型未返回可用的 POI 推荐")
        return [
            RecommendationItem(
                poi_id=poi_id,
                reason=(
                    generated_by_id[poi_id].reason
                    if poi_id in generated_by_id
                    else "这是你选择的点位，已由系统保留。"
                ),
                order=index,
            )
            for index, poi_id in enumerate(ordered_ids, start=1)
        ]

