from datetime import date

from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView

from api.models import Aspect, BuildOfDay, Hero, Item
from api.serializers import (
    HeroSerializer,
    ItemSerializer,
    RandomizeBuildRequestSerializer,
)

from .data import generate_skill_build


class HeroesListView(APIView):
    """
    GET: Return all available heroes for selection.
    """

    def get(self, request):
        heroes = Hero.objects.all().order_by("name")
        return Response(HeroSerializer(heroes, many=True).data, status=status.HTTP_200_OK)


class RandomizeBuildView(APIView):
    """
    POST: Generate a random Dota 2 build with hero, items, and optionally skills/aspects.
    """

    def get_serializer(self, *args, **kwargs):
        return RandomizeBuildRequestSerializer(*args, **kwargs)

    def post(self, request):
        serializer = RandomizeBuildRequestSerializer(data=request.data)
        if not serializer.is_valid():
            return Response(
                {"message": self._format_errors(serializer.errors)},
                status=status.HTTP_400_BAD_REQUEST,
            )

        validated_data = serializer.validated_data
        include_skills = validated_data["includeSkills"]
        include_aspect = validated_data["includeAspect"]
        items_count = validated_data["itemsCount"]
        hero_id = validated_data.get("heroId")

        hero_count = Hero.objects.count()
        item_count = Item.objects.count()

        if hero_count == 0:
            return Response(
                {"message": "No heroes available. Load data first."},
                status=status.HTTP_400_BAD_REQUEST,
            )

        if item_count < items_count:
            return Response(
                {"message": f"Not enough items available. Requested {items_count}, found {item_count}."},
                status=status.HTTP_400_BAD_REQUEST,
            )

        if hero_id:
            hero_obj = Hero.objects.filter(id=hero_id).first()
            if not hero_obj:
                return Response(
                    {"message": f"Hero with id {hero_id} not found."},
                    status=status.HTTP_400_BAD_REQUEST,
                )
        else:
            hero_obj = Hero.objects.order_by("?").first()
        item_objs = Item.objects.order_by("?")[:items_count]

        response_data = {
            "hero": HeroSerializer(hero_obj).data,
            "items": ItemSerializer(item_objs, many=True).data,
        }

        if include_skills:
            response_data["skillBuild"] = generate_skill_build()

        if include_aspect:
            hero_aspects = Aspect.objects.filter(hero=hero_obj)
            if hero_aspects.exists():
                aspect_obj = hero_aspects.order_by("?").first()
                response_data["aspect"] = aspect_obj.name

        return Response(response_data, status=status.HTTP_200_OK)

    def _format_errors(self, errors: dict) -> str:
        """Format serializer errors into a readable message."""
        messages = []
        for field, field_errors in errors.items():
            for error in field_errors:
                if field == "itemsCount" and "greater than or equal to" in str(error):
                    messages.append("itemsCount must be at least 1.")
                elif "valid" in str(error).lower() or "required" in str(error).lower():
                    if field in ("includeSkills", "includeAspect"):
                        messages.append("includeSkills and includeAspect must be boolean.")
                    elif field == "itemsCount":
                        messages.append("itemsCount must be a number.")
                else:
                    messages.append(f"{field}: {error}")
        return messages[0] if messages else "Invalid request data."


class BuildOfDayView(APIView):
    """
    GET: Return the build of the day. Generates one if it doesn't exist for today.
    """

    ITEMS_COUNT = 6

    def get(self, request):
        today = date.today()

        build = BuildOfDay.objects.filter(date=today).first()
        if not build:
            build = self._generate_build_of_day(today)
            if build is None:
                return Response(
                    {"message": "Unable to generate build. Load data first."},
                    status=status.HTTP_400_BAD_REQUEST,
                )

        response_data = {
            "date": build.date.isoformat(),
            "hero": HeroSerializer(build.hero).data,
            "items": ItemSerializer(build.items.all(), many=True).data,
            "skillBuild": build.skill_build,
        }

        if build.aspect:
            response_data["aspect"] = build.aspect

        return Response(response_data, status=status.HTTP_200_OK)

    def _generate_build_of_day(self, today: date) -> BuildOfDay | None:
        """Generate and save a new build of the day."""
        hero_count = Hero.objects.count()
        item_count = Item.objects.count()

        if hero_count == 0 or item_count < self.ITEMS_COUNT:
            return None

        hero_obj = Hero.objects.order_by("?").first()
        item_objs = list(Item.objects.order_by("?")[: self.ITEMS_COUNT])

        aspect_name = None
        hero_aspects = Aspect.objects.filter(hero=hero_obj)
        if hero_aspects.exists():
            aspect_obj = hero_aspects.order_by("?").first()
            aspect_name = aspect_obj.name

        build = BuildOfDay.objects.create(
            date=today,
            hero=hero_obj,
            skill_build=generate_skill_build(),
            aspect=aspect_name,
        )
        build.items.set(item_objs)

        return build
