from django.contrib.auth.decorators import login_required, user_passes_test
from django.db import transaction
from django.db.models import Q
from django.http import HttpResponseRedirect, JsonResponse
from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils.html import format_html
from django.utils.translation import gettext_lazy
from esi.decorators import token_required
from esi.errors import TokenError

from allianceauth.authentication.models import CharacterOwnership
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo
from allianceauth.services.hooks import get_extension_logger

from buybackprogram.forms import LocationForm, ProgramForm, ReverseProgramForm
from buybackprogram.models import (
    HangarDivision,
    Location,
    Owner,
    Program,
    ReverseProgram,
    WalletDivision,
)
from buybackprogram.tasks import (
    update_contracts_for_owner,
    update_hangar_divisions_for_owner,
    update_hangar_for_owner,
)
from buybackprogram.utils import can_manage_program_test, messages_plus

logger = get_extension_logger(__name__)


@login_required
@user_passes_test(can_manage_program_test)
@token_required(
    scopes=[
        "esi-contracts.read_character_contracts.v1",
        "esi-contracts.read_corporation_contracts.v1",
        "esi-universe.read_structures.v1",
        "esi-wallet.read_corporation_wallets.v1",
        "esi-corporations.read_divisions.v1",
        "esi-assets.read_corporation_assets.v1",
    ]
)
def setup(request, token):
    success = True
    token_char = EveCharacter.objects.get(character_id=token.character_id)

    try:
        owned_char = CharacterOwnership.objects.get(
            user=request.user, character=token_char
        )
    except CharacterOwnership.DoesNotExist:
        messages_plus.error(
            request,
            format_html(
                gettext_lazy(
                    "You can only use your main or alt characters "
                    "to add corporations. "
                    "However, character %s is neither. "
                )
                % format_html("<strong>{}</strong>", token_char.character_name)
            ),
        )
        success = False

    if success:
        try:
            corporation = EveCorporationInfo.objects.get(
                corporation_id=token_char.corporation_id
            )
        except EveCorporationInfo.DoesNotExist:
            corporation = EveCorporationInfo.objects.create_corporation(
                token_char.corporation_id
            )

        with transaction.atomic():
            owner, _ = Owner.objects.update_or_create(
                corporation=corporation, character=owned_char, user=request.user
            )

            owner.save()

        messages_plus.info(
            request,
            format_html(
                gettext_lazy(
                    "%(corporation)s has been added with %(character)s "
                    "as program manager. "
                )
                % {
                    "corporation": format_html(
                        "<strong>{}</strong>", owner.corporation
                    ),
                    "character": format_html(
                        "<strong>{}</strong>", owner.character.character.character_name
                    ),
                }
            ),
        )

    return redirect("buybackprogram:index")


@login_required
@user_passes_test(can_manage_program_test)
def program_add(request):
    form = ProgramForm(request.POST or None, user=request.user)

    # Smart Sync: Ensure all of the user's corporations have wallets loaded
    owners = Owner.objects.filter(user=request.user)
    for owner in owners:
        # Check if this specific owner has any cached wallets
        if not owner.wallets.exists():
            try:
                # Force an immediate ESI sync before loading the form
                owner.update_wallets_esi()
            except TokenError:
                # The user lacks scopes or permissions. Safely ignore it so the page still loads.
                pass
            except Exception as e:
                # Catch any network errors
                logger.error(
                    f"Unexpected error during wallet smart sync for {owner}: {e}"
                )

    if request.POST and form.is_valid():
        form = ProgramForm(request.POST, user=request.user)

        new_program = form.save()

        messages_plus.success(
            request,
            format_html(
                gettext_lazy("New program created at %(location)s")
                % {
                    "location": format_html(
                        "<strong>{}</strong>", new_program.location
                    ),
                }
            ),
        )

        return HttpResponseRedirect(reverse("buybackprogram:index"))

    return render(request, "buybackprogram/program_add.html", {"form": form})


@login_required
@user_passes_test(can_manage_program_test)
def program_edit(request, program_pk):
    instance = Program.objects.get(pk=program_pk)

    if not instance.user_can_manage(request.user):
        messages_plus.error(
            request, "You do not have permission to manage this program."
        )
        return redirect("buybackprogram:program_special_taxes", program_pk=instance.id)

    if request.method == "POST":
        form = ProgramForm(request.POST, user=request.user)

        logger.debug(
            "Received POST request containing update program form, is valid: %s"
            % form.is_valid()
        )

        if form.is_valid():
            form = ProgramForm(request.POST, instance=instance, user=request.user)

            updated_program = form.save()

            if updated_program.name:
                messages_plus.success(
                    request,
                    format_html(
                        gettext_lazy("Program %(name)s updated")
                        % {
                            "name": format_html(
                                "<strong>{}</strong>", updated_program.name
                            ),
                        }
                    ),
                )
            else:
                messages_plus.success(
                    request,
                    format_html(gettext_lazy("Program updated")),
                )

            return HttpResponseRedirect(reverse("buybackprogram:index"))

    else:
        form = ProgramForm(instance=instance, user=request.user)

    context = {
        "program": instance,
        "form": form,
    }

    return render(request, "buybackprogram/program_edit.html", context)


@login_required
@user_passes_test(can_manage_program_test)
def location_add(request):
    locations = Location.objects.all()

    if request.method != "POST":
        form = LocationForm(user=request.user)
    else:
        # Safely get the ID, defaulting to None if it's empty
        raw_solar_system = request.POST.get("eve_solar_system")
        solar_system_id = (
            int(raw_solar_system)
            if raw_solar_system and raw_solar_system.isdigit()
            else None
        )

        # Now pass the clean integer or None to the form
        form = LocationForm(request.POST, value=solar_system_id, user=request.user)

        if form.is_valid():
            # Use the data from the form
            eve_solar_system = form.cleaned_data["eve_solar_system"]
            name = form.cleaned_data["name"]
            structure_id = form.cleaned_data["structure_id"] or None

            owner = Owner.objects.filter(user=request.user).first()

            # update_or_create can handle eve_solar_system=None if your model allows it
            location_obj, created = Location.objects.update_or_create(
                eve_solar_system=eve_solar_system,
                name=name,
                structure_id=structure_id,
                owner=owner,
                defaults={
                    "eve_solar_system": eve_solar_system,
                    "name": name,
                    "structure_id": structure_id,
                    "owner": owner,
                },
            )

            if created:
                # Use a safe string for the message
                system_name = eve_solar_system.name if eve_solar_system else "None"
                messages_plus.success(
                    request,
                    format_html(
                        "Added location for <strong>{}</strong> in system <strong>{}</strong>",
                        name,
                        system_name,
                    ),
                )

            return HttpResponseRedirect(request.path_info)

    context = {
        "locations": locations,
        "form": form,
    }

    return render(request, "buybackprogram/location_add.html", context)


@login_required
@user_passes_test(can_manage_program_test)
def location_remove(request, location_pk):
    location = Location.objects.get(pk=location_pk)

    if location.owner.user == request.user or request.user.has_perm(
        "buybackprogram.manage_all_programs"
    ):
        location.delete()

        messages_plus.warning(
            request,
            format_html(
                gettext_lazy(
                    "Deleted location %(location)s. Any programs affiliated with this location has been removed as well."
                )
                % {
                    "location": format_html(
                        "<strong>{}</strong>", location.eve_solar_system
                    ),
                }
            ),
        )

    else:
        messages_plus.error(
            request,
            format_html(
                gettext_lazy(
                    "You did not create this location and thus you can't delete it."
                )
            ),
        )

    return redirect("/buybackprogram/location_add")


@login_required
@user_passes_test(can_manage_program_test)
def program_remove(request, program_pk):
    program = Program.objects.get(pk=program_pk)

    if program.user_can_manage(request.user):
        program.delete()
        messages_plus.warning(
            request,
            format_html(
                gettext_lazy("Deleted program %(name)s")
                % {"name": format_html("<strong>{}</strong>", program.name)}
            ),
        )
    else:
        messages_plus.error(
            request,
            "You do not have permission to delete this program.",
        )
    return redirect("buybackprogram:index")


@login_required
@user_passes_test(can_manage_program_test)
def manual_contract_sync(request):
    """Triggers the Celery task to fetch contracts for the logged-in manager."""
    owners = Owner.objects.filter(user=request.user)

    if not owners.exists():
        messages_plus.error(request, "No manager profiles found.")
        return redirect("buybackprogram:program_stats")

    count = 0
    for owner in owners:
        # Pass the kwarg explicitly to match your tasks.py definition
        update_contracts_for_owner.delay(owner_pk=owner.pk)

        count += 1

    messages_plus.success(
        request,
        f"Contract sync started in the background for {count} manager(s). "
        "The outstanding contracts list will update shortly.",
    )

    return redirect("buybackprogram:program_stats")


def reverse_index(request):
    if request.user.is_authenticated:
        if request.user.has_perm("buybackprogram.manage_all_programs"):
            programs = ReverseProgram.objects.all()
        else:
            programs = (
                ReverseProgram.objects.filter(
                    Q(restricted_to_group__in=request.user.groups.all())
                    | Q(restricted_to_group__isnull=True)
                    | Q(owner__user=request.user)
                )
                .filter(
                    Q(restricted_to_state=request.user.profile.state)
                    | Q(restricted_to_state__isnull=True)
                    | Q(owner__user=request.user)
                )
                .distinct()
            )
    else:
        programs = ReverseProgram.objects.filter(is_public=True)

    context = {
        "programs": programs,
        "is_reverse_section": True,
    }

    return render(request, "buybackprogram/reverse_program/reverse_index.html", context)


@login_required
@user_passes_test(can_manage_program_test)
def reverse_program_add(request):
    owners = Owner.objects.filter(user=request.user)
    hangars_missing = False

    for owner in owners:
        if not owner.hangars.exists():
            hangars_missing = True
            # Queue async fetch — don't block the request on ESI
            update_hangar_divisions_for_owner.delay(owner_pk=owner.pk)
            logger.debug(
                f"reverse_program_add: no hangars for {owner}, queued ESI sync"
            )

    form = ReverseProgramForm(request.POST or None, user=request.user)

    if request.POST and form.is_valid():
        new_program = form.save()

        logger.debug(
            f"New reverse program {new_program.name} created, queuing hangar sync for owner {new_program.owner}"
        )
        update_hangar_for_owner.delay(owner_pk=new_program.owner.pk)
        update_hangar_divisions_for_owner.delay(owner_pk=new_program.owner.pk)

        messages_plus.success(
            request,
            format_html(
                gettext_lazy(
                    "New reverse buyback program %(name)s created. Hangar stock is being synced in the background."
                )
                % {"name": format_html("<strong>{}</strong>", new_program.name)}
            ),
        )

        return HttpResponseRedirect(reverse("buybackprogram:reverse_index"))

    if hangars_missing:
        messages_plus.warning(
            request,
            "Hangar divisions are being loaded from ESI. "
            "If the Hangar Division dropdown is empty, wait a moment and refresh this page.",
        )

    context = {
        "form": form,
        "is_reverse_section": True,
    }

    return render(
        request, "buybackprogram/reverse_program/reverse_program_add.html", context
    )


@login_required
@user_passes_test(can_manage_program_test)
def reverse_program_edit(request, program_pk):
    instance = ReverseProgram.objects.get(pk=program_pk)

    if instance.owner.user != request.user and not request.user.has_perm(
        "buybackprogram.manage_all_programs"
    ):
        messages_plus.error(
            request, "You do not have permission to manage this program."
        )
        return redirect("buybackprogram:reverse_index")

    if request.method == "POST":
        form = ReverseProgramForm(request.POST, instance=instance, user=request.user)

        if form.is_valid():
            updated_program = form.save()

            messages_plus.success(
                request,
                format_html(
                    gettext_lazy("Reverse program %(name)s updated")
                    % {"name": format_html("<strong>{}</strong>", updated_program.name)}
                ),
            )

            return HttpResponseRedirect(reverse("buybackprogram:reverse_index"))
    else:
        form = ReverseProgramForm(instance=instance, user=request.user)

    context = {
        "program": instance,
        "form": form,
        "is_reverse_section": True,
    }

    return render(
        request, "buybackprogram/reverse_program/reverse_program_edit.html", context
    )


@login_required
@user_passes_test(can_manage_program_test)
def reverse_program_remove(request, program_pk):
    program = ReverseProgram.objects.get(pk=program_pk)

    if program.owner.user == request.user or request.user.has_perm(
        "buybackprogram.manage_all_programs"
    ):
        program.delete()

        messages_plus.warning(
            request,
            format_html(
                gettext_lazy("Deleted reverse program %(name)s")
                % {"name": format_html("<strong>{}</strong>", program.name)}
            ),
        )
    else:
        messages_plus.error(
            request,
            "You do not have permission to delete this program.",
        )

    return redirect("buybackprogram:reverse_index")


@login_required
@user_passes_test(can_manage_program_test)
def manual_hangar_sync(request):
    owners = Owner.objects.filter(user=request.user)

    if not owners.exists():
        messages_plus.error(request, "No manager profiles found.")
        return redirect("buybackprogram:reverse_index")

    count = 0
    for owner in owners:
        if ReverseProgram.objects.filter(owner=owner).exists():
            update_hangar_for_owner.delay(owner_pk=owner.pk)
            update_hangar_divisions_for_owner.delay(owner_pk=owner.pk)
            count += 1

    if count:
        messages_plus.success(
            request,
            f"Hangar stock sync started in the background for {count} manager(s). "
            "Stock will update shortly.",
        )
    else:
        messages_plus.warning(
            request,
            "No reverse programs found for your managers.",
        )

    return redirect("buybackprogram:reverse_index")


@login_required
@user_passes_test(can_manage_program_test)
def manual_reverse_contract_sync(request):
    owners = Owner.objects.filter(user=request.user)

    if not owners.exists():
        messages_plus.error(request, "No manager profiles found.")
        return redirect("buybackprogram:reverse_index")

    count = 0
    for owner in owners:
        if ReverseProgram.objects.filter(owner=owner).exists():
            update_contracts_for_owner.delay(owner_pk=owner.pk)
            count += 1

    if count:
        messages_plus.success(
            request,
            f"Reverse contract sync started for {count} manager(s). "
            "Contracts will update shortly.",
        )
    else:
        messages_plus.warning(request, "No reverse programs found for your managers.")

    return redirect("buybackprogram:reverse_index")


@login_required
@user_passes_test(can_manage_program_test)
def hangar_division_autocomplete(request):
    owner_id = request.GET.get("owner_id")
    if request.user.has_perm("buybackprogram.manage_all_programs"):
        hangars = HangarDivision.objects.filter(owner_id=owner_id)
    else:
        hangars = HangarDivision.objects.filter(
            owner_id=owner_id,
            owner__user=request.user,
        )
    data = [{"id": h.id, "name": h.name} for h in hangars]
    return JsonResponse(data, safe=False)


@login_required
@user_passes_test(can_manage_program_test)
def wallet_autocomplete(request):
    owner_id = request.GET.get("owner_id")
    if request.user.has_perm("buybackprogram.manage_all_programs"):
        wallets = WalletDivision.objects.filter(owner_id=owner_id)
    else:
        wallets = WalletDivision.objects.filter(
            owner_id=owner_id,
            owner__user=request.user,
        )
    data = [{"id": w.id, "name": str(w)} for w in wallets]
    return JsonResponse(data, safe=False)
