import json
from datetime import datetime

from corptools.models import EveItemType
from ninja import NinjaAPI, Schema

from django.db import transaction
from django.db.models import Prefetch
from django.http import HttpRequest
from django.shortcuts import get_object_or_404
from django.utils.timezone import now

from allianceauth.eveonline.models import EveCharacter
from allianceauth.framework.api.evecharacter import (
    get_main_character_from_evecharacter,
)
from allianceauth.framework.api.user import (
    get_all_characters_from_user, get_main_character_from_user,
)
from allianceauth.services.hooks import get_extension_logger

from incursions.api.helpers.fittings import FittingParser
from incursions.api.helpers.sse import SSEClient, SSEEvent
from incursions.api.schema import CharacterSchema, HullSchema
from incursions.app_settings import SSE_SECRET, SSE_SITE_URL
from incursions.models.waitlist import (
    ActiveFleet, Fitting, FleetSquad, ImplantSet, Waitlist, WaitlistCategory,
    WaitlistCategoryRule, WaitlistEntry, WaitlistEntryFit,
)
from incursions.providers import get_character_implants, invite_to_fleet

logger = get_extension_logger(__name__)
sse_client = SSEClient(SSE_SITE_URL, SSE_SECRET)


# Schemas
class WaitlistEntryFitSchema(Schema):
    id: int
    approved: bool
    category: str
    dna: str
    hull: HullSchema | None = None
    character: CharacterSchema | None = None
    hours_in_fleet: int | None = None
    review_comment: str | None = None
    implants: list[int] | None = None
    fit_analysis: dict | None = None
    is_alt: bool
    messagexup: str | None = None
    tags: list[str] = []


class WaitlistEntrySchema(Schema):
    id: int
    character: CharacterSchema | None = None
    joined_at: datetime
    can_remove: bool
    fits: list[WaitlistEntryFitSchema]


class WaitlistResponse(Schema):
    open: bool
    waitlist: list[WaitlistEntrySchema] | None = None
    categories: list[str]


class XupRequest(Schema):
    waitlist_id: int
    character_id: int
    eft: str
    is_alt: bool
    messagexup: str


class RemoveXRequest(Schema):
    id: int


class RemoveFitRequest(Schema):
    id: int


class InviteRequest(Schema):
    id: int
    character_id: int


class ApproveRequest(Schema):
    id: int


class RejectRequest(Schema):
    id: int
    review_comment: str


class SetOpenRequest(Schema):
    open: bool
    waitlist_id: int


class EmptyWaitlistRequest(Schema):
    waitlist_id: int


class WaitlistUpdateSchema(Schema):
    waitlist_id: int


api = NinjaAPI()


def setup(api: NinjaAPI) -> None:
    WaitlistAPIEndpoints(api)


class WaitlistAPIEndpoints:
    tags = ["Waitlist"]

    def __init__(self, api: NinjaAPI) -> None:

        @api.get(
            "/waitlist",
            response={200: WaitlistResponse, 403: dict, 404: dict},
            tags=self.tags,
        )
        def get_waitlist(request: HttpRequest) -> WaitlistResponse | tuple[int, dict]:
            logger.info("Fetching waitlist for user %s", request.user)
            if not request.user.has_perm("incursions.basic_waitlist"):
                logger.warning("User %s does not have permission incursions.basic_waitlist", request.user)
                return 403, {"error": "Permission denied"}

            waitlist = Waitlist.get_solo()
            categories: list[str] = list(WaitlistCategory.objects.values_list("name", flat=True))
            request_main_character = get_main_character_from_user(request.user)
            context_manager: bool = request.user.has_perm("incursions.waitlist_manage_waitlist")
            context_wl_approver: bool = context_manager or request.user.has_perm("incursions.waitlist_manage_waitlist_approve_fits")
            context_implants: bool = context_wl_approver or request.user.has_perm("incursions.waitlist_implants_view")
            context_stats: bool = context_wl_approver or request.user.has_perm("incursions.waitlist_stats_view")
            context_quantity: bool = context_wl_approver or request.user.has_perm("incursions.waitlist_context_a")
            context_hull: bool = context_wl_approver or request.user.has_perm("incursions.waitlist_context_b")
            context_time: bool = context_wl_approver or request.user.has_perm("incursions.waitlist_context_c")
            context_name: bool = context_wl_approver or request.user.has_perm("incursions.waitlist_context_d")

            if not waitlist.is_open:
                logger.info("Waitlist is closed.")
                return WaitlistResponse(open=False, waitlist=None, categories=categories)

            waitlist_entries = (
                WaitlistEntry.objects.filter(waitlist=waitlist)
                .select_related("main_character")
                .prefetch_related(
                    Prefetch(
                        "waitlistentryfit_set",
                        queryset=WaitlistEntryFit.objects.select_related(
                            "fit", "fit__ship", "character", "category", "implant_set"
                        ).order_by("pk"),
                    )
                )
                .order_by("pk")
            )

            response_waitlist: list[WaitlistEntrySchema] = []
            for waitlist_entry in waitlist_entries:
                fits_data: list[WaitlistEntryFitSchema] = []
                is_self: bool = request_main_character == waitlist_entry.main_character
                for wl_fit in waitlist_entry.waitlistentryfit_set.all():
                    if wl_fit.implant_set and wl_fit.implant_set.implants:
                        implants_list = json.loads(wl_fit.implant_set.implants)
                    else:
                        implants_list: list[str] = []
                    try:
                        fit_analysis_data = json.loads(wl_fit.fit_analysis)
                    except Exception as e:
                        logger.error("Error parsing fit_analysis for fit %s: %s", wl_fit.pk, e)
                        fit_analysis_data = None
                    fits_data.append(
                        WaitlistEntryFitSchema(
                            id=wl_fit.pk,
                            approved=wl_fit.approved,
                            category=wl_fit.category.name,
                            hull=HullSchema(
                                id=wl_fit.fit.ship.type_id
                                if (is_self or context_wl_approver or context_hull)
                                else 0,
                                name=wl_fit.fit.ship.name
                                if (is_self or context_wl_approver or context_hull)
                                else "Hidden",
                            ),
                            character=CharacterSchema(
                                character_name=wl_fit.character.character_name
                                if (is_self or context_name)
                                else "Hidden",
                                character_id=wl_fit.character.character_id
                                if (is_self or context_name)
                                else 0,
                            ),
                            hours_in_fleet=(wl_fit.cached_time_in_fleet // 3600)
                            if context_stats
                            else 0,
                            review_comment=wl_fit.review_comment if (is_self or context_wl_approver) else None,
                            dna=wl_fit.fit.dna if (is_self or context_wl_approver) else "",
                            implants=implants_list if (is_self or context_wl_approver or context_implants) else [],
                            fit_analysis=fit_analysis_data if (is_self or context_wl_approver or context_implants) else None,
                            is_alt=wl_fit.is_alt if (is_self or context_wl_approver or context_quantity) else False,
                            messagexup=wl_fit.messagexup if (is_self or context_wl_approver) else None,
                            tags=[tag for tag in wl_fit.tags.split(",") if tag],
                        )
                    )
                response_waitlist.append(
                    WaitlistEntrySchema(
                        id=waitlist_entry.pk,
                        character=CharacterSchema(
                            character_name=waitlist_entry.main_character.character_name
                            if (is_self or context_name)
                            else "Hidden",
                            character_id=waitlist_entry.main_character.character_id
                            if (is_self or context_name)
                            else 0,
                        ),
                        joined_at=waitlist_entry.joined_at if (is_self or context_time) else now(),
                        can_remove=True if (is_self or context_wl_approver) else False,
                        fits=fits_data,
                    )
                )
            logger.info("Returning waitlist with %d entries", len(response_waitlist))
            return WaitlistResponse(open=True, waitlist=response_waitlist, categories=categories)

        @api.post(
            "/waitlist/xup",
            response={200: dict, 403: dict, 404: dict},
            tags=self.tags,
        )
        def xup(request: HttpRequest, payload: XupRequest) -> tuple[int, dict]:
            logger.info(
                "Processing xup for waitlist_id: %s and character_id: %s",
                payload.waitlist_id,
                payload.character_id,
            )
            if not request.user.has_perm("incursions.basic_waitlist"):
                logger.warning("User %s lacks basic_waitlist permission", request.user)
                return 403, {"error": "Permission denied"}

            waitlist = get_object_or_404(Waitlist, pk=payload.waitlist_id)
            character = get_object_or_404(EveCharacter, character_id=payload.character_id)
            fitting_parser = FittingParser.from_eft(payload.eft)
            with transaction.atomic():
                fitting, _ = Fitting.objects.get_or_create(
                    dna=fitting_parser.to_dna(),
                    ship=EveItemType.objects.get_or_create_from_esi(fitting_parser.hull)[0],
                )
                implant_set, _ = ImplantSet.objects.get_or_create(
                    implants=list(get_character_implants(character.character_id)[0])
                )
                try:
                    rule = (
                        WaitlistCategoryRule.objects.select_related("waitlist_category")
                        .get(ship=fitting.ship)
                    )
                    waitlist_category = rule.waitlist_category
                except WaitlistCategoryRule.DoesNotExist:
                    waitlist_category, _ = WaitlistCategory.objects.get_or_create(name="Other")
                waitlist_entry, _ = WaitlistEntry.objects.get_or_create(
                    main_character=character,
                    waitlist=waitlist,
                    defaults={"joined_at": now()},
                )
                new_fit = WaitlistEntryFit.objects.create(
                    character=character,
                    waitlist=waitlist,
                    fit=fitting,
                    implant_set=implant_set,
                    approved=False,
                    tags="",
                    category=waitlist_category,
                    cached_time_in_fleet=0,
                    is_alt=payload.is_alt,
                    messagexup=payload.messagexup,
                )
            sse_payload = WaitlistUpdateSchema(waitlist_id=waitlist.pk).model_dump()
            sse_client.submit(
                [SSEEvent.new_json(topic="waitlist", event="waitlist_update", data=sse_payload)]
            )
            boss_char = get_main_character_from_evecharacter(ActiveFleet.get_solo().fleet.boss)
            sse_client.submit(
                [
                    SSEEvent.new(
                        topic=f"account;{boss_char.character_id}",
                        event="message",
                        data="New x-up in waitlist",
                    )
                ]
            )
            logger.info("X-up recorded with fit_id %s", new_fit.pk)
            return 200, {"status": "X-up recorded", "fit_id": new_fit.pk}

        @api.post(
            "/waitlist/remove_x",
            response={200: dict, 403: dict},
            tags=self.tags,
        )
        def remove_x(request: HttpRequest, payload: RemoveXRequest) -> tuple[int, dict]:
            logger.info("Removing waitlist entry x with id %s", payload.id)
            waitlist_entry = get_object_or_404(WaitlistEntry, pk=payload.id)
            waitlist = get_object_or_404(Waitlist, pk=waitlist_entry.waitlist.pk)
            if not (
                waitlist_entry.main_character == get_main_character_from_user(request.user)
                or request.user.has_perm("incursions.manage_waitlist")
            ):
                logger.warning("User %s not authorized to remove waitlist entry %s", request.user, payload.id)
                return 403, {"error": "Permission denied"}
            with transaction.atomic():
                waitlist_entry.delete()
            sse_payload = WaitlistUpdateSchema(waitlist_id=waitlist.pk).model_dump()
            sse_client.submit(
                [SSEEvent.new_json(topic="waitlist", event="waitlist_update", data=sse_payload)]
            )
            logger.info("Waitlist entry %s removed", payload.id)
            return 200, {"status": "Waitlist X-Up removed"}

        @api.post(
            "/waitlist/remove_fit",
            response={200: dict, 403: dict},
            tags=self.tags,
        )
        def remove_fit(request: HttpRequest, payload: RemoveFitRequest) -> tuple[int, dict]:
            logger.info("Removing fit with id %s", payload.id)
            entry = get_object_or_404(WaitlistEntryFit, pk=payload.id)
            waitlist_entry = get_object_or_404(WaitlistEntry, pk=entry.waitlist_entry.pk)
            waitlist = get_object_or_404(Waitlist, pk=waitlist_entry.waitlist.pk)
            if not (entry.character in get_all_characters_from_user(request.user) or request.user.has_perm("incursions.manage_waitlist")):
                logger.warning("User %s not authorized to remove fit %s", request.user, payload.id)
                return 403, {"error": "Permission denied"}
            with transaction.atomic():
                entry.delete()
                if not WaitlistEntryFit.objects.filter(waitlist_entry=waitlist_entry).exists():
                    waitlist_entry.delete()
            sse_payload = WaitlistUpdateSchema(waitlist_id=waitlist.pk).model_dump()
            sse_client.submit([SSEEvent.new_json(topic="waitlist", event="waitlist_update", data=sse_payload)])
            logger.info("Waitlist fit %s removed", payload.id)
            return 200, {"status": "Waitlist X-Up Fit removed"}

        @api.post("/waitlist/invite", tags=self.tags)
        def invite(request: HttpRequest, payload: InviteRequest) -> dict:
            logger.info("Inviting character %s for waitlist entry fit id %s", payload.character_id, payload.id)
            if not request.user.has_perm("incursions.manage_waitlist"):
                logger.warning("User %s not authorized to invite", request.user)
                return 403, {"error": "Permission denied"}
            active_fleet = ActiveFleet.get_solo()
            waitlist_entry_fit = get_object_or_404(WaitlistEntryFit, pk=payload.id)
            fleet_squad = get_object_or_404(
                FleetSquad, fleet=active_fleet.fleet, name=waitlist_entry_fit.category.name
            )
            squad_id = fleet_squad.squad_id
            wing_id = fleet_squad.wing_id
            invite_to_fleet(
                boss_character_id=active_fleet.fleet.boss.pk,
                fleet_id=active_fleet.fleet.pk,
                character_id=payload.character_id,
                squad_id=squad_id if squad_id else None,
                wing_id=wing_id if wing_id else None,
                role="squad_member",
            )
            invited_char = get_main_character_from_evecharacter(waitlist_entry_fit.character)
            sse_client.submit([SSEEvent.new(
                topic=f"account;{invited_char.character_id}",
                event="wakeup",
                data=f"{active_fleet.fleet.boss.character_name} has invited your {waitlist_entry_fit.fit.ship.name} to fleet.",
            )])
            logger.info("Character %s invited for waitlist entry %s", payload.character_id, payload.id)
            return {"status": f"Character {payload.character_id} invited to waitlist entry {payload.id}"}

        @api.post("/waitlist/approve", tags=self.tags)
        def approve_fit(request: HttpRequest, payload: ApproveRequest) -> dict:
            logger.info("Approving fit with id %s", payload.id)
            if not (
                request.user.has_perm("incursions.manage_waitlist")
                or request.user.has_perm("incursions.waitlist_manage_waitlist_approve_fits")
            ):
                logger.warning("User %s not authorized to approve fit %s", request.user, payload.id)
                return 403, {"error": "Permission denied"}
            with transaction.atomic():
                fit = get_object_or_404(WaitlistEntryFit, pk=payload.id)
                fit.approved = True
                fit.save(update_fields=["approved"])
            sse_payload = WaitlistUpdateSchema(waitlist_id=fit.waitlist_entry.pk).model_dump()
            sse_client.submit(
                [SSEEvent.new_json(topic="waitlist", event="waitlist_update", data=sse_payload)]
            )
            logger.info("Fit %s approved", payload.id)
            return {"status": f"Fit {fit.pk} approved"}

        @api.post("/waitlist/reject", tags=self.tags)
        def reject_fit(request: HttpRequest, payload: RejectRequest) -> tuple[int, dict]:
            logger.info("Rejecting fit with id %s", payload.id)
            if not (
                request.user.has_perm("incursions.manage_waitlist")
                or request.user.has_perm("incursions.waitlist_manage_waitlist_approve_fits")
            ):
                logger.warning("User %s not authorized to reject fit %s", request.user, payload.id)
                return 403, {"error": "Permission denied"}
            with transaction.atomic():
                fit = get_object_or_404(WaitlistEntryFit, pk=payload.id)
                fit.approved = False
                fit.review_comment = payload.review_comment
                fit.save(update_fields=["approved", "review_comment"])
            sse_payload = WaitlistUpdateSchema(waitlist_id=fit.waitlist_entry.pk).model_dump()
            sse_client.submit(
                [SSEEvent.new_json(topic="waitlist", event="waitlist_update", data=sse_payload)]
            )
            logger.info("Fit %s rejected", payload.id)
            return 200, {"status": f"Fit {fit.pk} rejected"}

        @api.post("/waitlist/set_open", tags=self.tags)
        def set_open(request: HttpRequest, payload: SetOpenRequest) -> dict:
            logger.info("Setting waitlist %s open state to %s", payload.waitlist_id, payload.open)
            if not request.user.has_perm("incursions.manage_waitlist"):
                logger.warning("User %s not authorized to set open state", request.user)
                return 403, {"error": "Permission denied"}
            with transaction.atomic():
                w = get_object_or_404(Waitlist, pk=payload.waitlist_id)
                w.is_open = payload.open
                w.save(update_fields=["is_open"])
            sse_payload = WaitlistUpdateSchema(waitlist_id=w.pk).model_dump()
            sse_client.submit(
                [SSEEvent.new_json(topic="waitlist", event="open", data=sse_payload)]
            )
            sse_client.submit(
                [SSEEvent.new_json(topic="waitlist", event="waitlist_update", data=sse_payload)]
            )
            logger.info("Waitlist %s open state set to %s", payload.waitlist_id, payload.open)
            return 200, {"status": "OK"}

        @api.post("/waitlist/empty", tags=self.tags)
        def empty_waitlist(request: HttpRequest, payload: EmptyWaitlistRequest) -> dict:
            logger.info("Emptying waitlist with id %s", payload.waitlist_id)
            if not request.user.has_perm("incursions.manage_waitlist"):
                logger.warning("User %s not authorized to empty waitlist", request.user)
                return 403, {"error": "Permission denied"}
            with transaction.atomic():
                w = get_object_or_404(Waitlist, pk=payload.waitlist_id)
                WaitlistEntryFit.objects.filter(waitlist=w).delete()
            sse_payload = WaitlistUpdateSchema(waitlist_id=w.pk).model_dump()
            sse_client.submit(
                [SSEEvent.new_json(topic="waitlist", event="waitlist_update", data=sse_payload)]
            )
            logger.info("Waitlist %s emptied", payload.waitlist_id)
            return 200, {"status": "OK"}
