from decimal import Decimal
from datetime import date

from django.contrib.auth.decorators import login_required, permission_required
from django.contrib import messages
from django.shortcuts import redirect, render
from django.utils.translation import gettext_lazy as _t
from django.utils import timezone
from django.utils.formats import date_format

from allianceauth.authentication.models import CharacterOwnership
from allianceauth.services.hooks import get_extension_logger
from esi.decorators import token_required
from esi.clients import EsiClientProvider

from .models import (
    Corporation,
    CorpTokenLink,
    TaxConfig,
    CorpMonthStat,
    AllianceSettings,
)
from .tasks import pull_month_for_corp
from .esi import fetch_corp_public

logger = get_extension_logger(__name__)
esi = EsiClientProvider()

APP_LABEL = "rattingtax"
PERM_VIEW_ALL = f"{APP_LABEL}.view_all"
PERM_BASIC = f"{APP_LABEL}.basic_access"


def _fmt_percent(dec: Decimal) -> str:
    """Return '10' for 10.00; keep up to 2 decimals otherwise (e.g. '10.5')."""
    if dec == dec.to_integral_value():
        return str(int(dec))
    return "{:f}".format(dec.quantize(Decimal("0.01"))).rstrip("0").rstrip(".")


def _get_settings():
    """
    Return singleton settings object.
    """
    try:
        return AllianceSettings.get_solo()
    except Exception:
        obj = AllianceSettings.objects.first()
        if obj:
            return obj
        return AllianceSettings.objects.create()


def _visible_corps(user):
    """
    Corporations visible to the user in "my corporations" mode:
    only corporations explicitly linked to that user in RattingTax.
    """
    corp_ids = (
        CorpTokenLink.objects
        .filter(user=user)
        .values_list("corp_id", flat=True)
        .distinct()
    )
    return Corporation.objects.filter(id__in=corp_ids)


def _effective_tax_rate(corp: Corporation, default_rate: Decimal) -> Decimal:
    """
    Return the effective configured tax rate for a corporation:
    per-corp override if set, otherwise global default.
    """
    cfg = getattr(corp, "tax_cfg", None)
    if cfg and cfg.tax_rate_percent is not None:
        return Decimal(cfg.tax_rate_percent)
    return Decimal(default_rate or 0)


@login_required
@permission_required(PERM_BASIC, raise_exception=True)
def dashboard(request):
    """
    Dashboard filtered only by year/month.
    - Normal user: sees only their corporations
    - view_all: can toggle between own and all corporations
    """

    now = timezone.now()

    # --- show_all toggle ---
    show_all = False
    if request.user.has_perm(PERM_VIEW_ALL):
        show_all = request.GET.get("show_all") == "1"

    corp_qs = Corporation.objects.all() if show_all else _visible_corps(request.user)

    corps = list(
        corp_qs
        .select_related("tax_cfg")
        .order_by("name")
    )

    # --- available months ---
    avail = (
        CorpMonthStat.objects
        .filter(corp__in=corps)
        .values_list("year", "month")
        .distinct()
        .order_by("-year", "-month")
    )

    years = sorted({y for y, _ in avail}, reverse=True)

    months_for_year = {}
    for y, m in avail:
        months_for_year.setdefault(y, set()).add(m)

    selected_year = int(request.GET.get("year") or (years[0] if years else now.year))
    months = sorted(months_for_year.get(selected_year, {now.month}), reverse=True)
    selected_month = int(request.GET.get("month") or (months[0] if months else now.month))

    month_options = [
        {"value": m, "label": date_format(date(selected_year, m, 1), "F")}
        for m in months
    ]

    # --- stats map (IMPORTANT FIX) ---
    stats_map = {
        st.corp_id: st
        for st in CorpMonthStat.objects
        .filter(
            year=selected_year,
            month=selected_month,
            corp__in=corps,
        )
        .select_related("corp", "corp__tax_cfg")
    }

    settings_obj = _get_settings()
    default_tax_rate = Decimal(settings_obj.default_tax_rate_percent or 0)
    flat_tax_reduction = Decimal(settings_obj.flat_tax_reduction or 0)

    rows = []
    for corp in corps:
        st = stats_map.get(corp.id)
        month_total = Decimal(st.corp_bounty_tax_amount or 0) if st else Decimal("0")

        effective_rate = _effective_tax_rate(corp, default_tax_rate)

        taxable_base = month_total - flat_tax_reduction
        if taxable_base < 0:
            taxable_base = Decimal("0")

        final_tax = taxable_base * (effective_rate / Decimal("100"))
        final_tax = final_tax.quantize(Decimal("0.01"))

        rows.append({
            "corp": corp,
            "month_total": f"{month_total:,.2f}",
            "taxable_base": f"{taxable_base:,.2f}",
            "effective_rate": _fmt_percent(effective_rate),
            "flat_tax_reduction": f"{flat_tax_reduction:,.2f}",
            "final_tax": f"{final_tax:,.2f}",
            "uses_override": bool(
                getattr(corp, "tax_cfg", None)
                and corp.tax_cfg.tax_rate_percent is not None
            ),
            "has_stat": st is not None,
        })

    context = {
        "years": years or [now.year],
        "months": [opt["value"] for opt in month_options] or [now.month],
        "month_options": month_options,
        "selected_year": selected_year,
        "selected_month": selected_month,
        "rows": rows,
        "default_tax_rate": _fmt_percent(default_tax_rate),
        "flat_tax_reduction": f"{flat_tax_reduction:,.2f}",
        "show_all": show_all,
        "can_view_all": request.user.has_perm(PERM_VIEW_ALL),
    }

    return render(request, "rattingtax/dashboard.html", context)


@login_required
@permission_required(PERM_VIEW_ALL, raise_exception=True)
def management(request):
    """
    Tax management panel.
    Uses view_all as the management permission.
    """
    settings_obj = _get_settings()

    if request.method == "POST":
        try:
            default_rate_raw = (request.POST.get("default_tax_rate_percent") or "0").strip()
            flat_reduction_raw = (request.POST.get("flat_tax_reduction") or "0").strip()

            settings_obj.default_tax_rate_percent = Decimal(default_rate_raw)
            settings_obj.flat_tax_reduction = int(flat_reduction_raw or 0)
            settings_obj.save()

            corps_for_save = Corporation.objects.all().select_related("tax_cfg")
            for corp in corps_for_save:
                field_name = f"corp_rate_{corp.id}"
                raw_value = (request.POST.get(field_name) or "").strip()

                cfg = getattr(corp, "tax_cfg", None)

                if raw_value == "":
                    if cfg and cfg.tax_rate_percent is not None:
                        cfg.tax_rate_percent = None
                        cfg.save(update_fields=["tax_rate_percent"])
                    elif not cfg:
                        TaxConfig.objects.create(corp=corp, tax_rate_percent=None)
                else:
                    rate_value = Decimal(raw_value)
                    if cfg:
                        if cfg.tax_rate_percent != rate_value:
                            cfg.tax_rate_percent = rate_value
                            cfg.save(update_fields=["tax_rate_percent"])
                    else:
                        TaxConfig.objects.create(corp=corp, tax_rate_percent=rate_value)

            messages.success(request, _t("Tax settings updated successfully."))
            return redirect("rattingtax:management")

        except Exception:
            logger.exception("Failed to save tax settings")
            messages.error(request, _t("Error while saving tax settings."))

    default_tax_rate = Decimal(settings_obj.default_tax_rate_percent or 0)
    flat_tax_reduction = Decimal(settings_obj.flat_tax_reduction or 0)

    corps = list(
        Corporation.objects
        .all()
        .select_related("tax_cfg")
        .order_by("name")
    )

    rows = []
    for corp in corps:
        cfg = getattr(corp, "tax_cfg", None)
        override_rate = cfg.tax_rate_percent if cfg else None
        effective_rate = _effective_tax_rate(corp, default_tax_rate)

        ingame_tax_rate = None
        try:
            corp_pub = fetch_corp_public(corp.corporation_id)
            raw_rate = corp_pub.get("tax_rate", 0) or 0
            ingame_tax_rate = Decimal(str(raw_rate)) * Decimal("100")
        except Exception:
            logger.warning(
                "Failed to fetch corp public info for %s",
                corp.corporation_id,
                exc_info=True,
            )

        rows.append({
            "corp": corp,
            "override_rate": _fmt_percent(override_rate) if override_rate is not None else "",
            "effective_rate": _fmt_percent(effective_rate),
            "uses_override": override_rate is not None,
            "ingame_tax_rate": _fmt_percent(ingame_tax_rate) if ingame_tax_rate is not None else "",
        })

    context = {
        "default_tax_rate": _fmt_percent(default_tax_rate),
        "flat_tax_reduction": f"{flat_tax_reduction:,.2f}",
        "flat_tax_reduction_raw": str(int(flat_tax_reduction)),
        "rows": rows,
    }
    return render(request, "rattingtax/management.html", context)

@login_required
@permission_required(PERM_BASIC, raise_exception=True)
def pull_history(request, corp_id, year, month):
    """
    Manually queue a pull for a historical month for a specific corporation.
    """
    try:
        corp = Corporation.objects.get(pk=corp_id)
    except Corporation.DoesNotExist:
        messages.error(request, _t("Corporation not found."))
        return redirect("rattingtax:dashboard")

    if not request.user.has_perm(PERM_VIEW_ALL):
        visible_ids = set(_visible_corps(request.user).values_list("id", flat=True))
        if corp.id not in visible_ids:
            messages.error(request, _t("You don’t have access to this corporation."))
            return redirect("rattingtax:dashboard")

    pull_month_for_corp.delay(corp.corporation_id, int(year), int(month))
    messages.success(request, _t("Pull queued for %(corp)s %(y)s-%(m)s.") % {
        "corp": corp.name,
        "y": year,
        "m": str(month).zfill(2),
    })
    return redirect("rattingtax:dashboard")


@login_required
@permission_required(PERM_BASIC, raise_exception=True)
@token_required(scopes=["esi-wallet.read_corporation_wallets.v1"])
def connect_corp_token(request, token):
    """
    Link the selected token's character to its corporation and queue initial sync.
    """
    try:
        char_id = token.character_id
        char_name = getattr(token, "character_name", "") or ""

        corp_id = None
        try:
            ownership = CharacterOwnership.objects.select_related("character").get(
                character__character_id=char_id
            )
            corp_id = getattr(ownership.character, "corporation_id", None)
        except CharacterOwnership.DoesNotExist:
            pass

        if not corp_id:
            char_resp = esi.client.Character.get_characters_character_id(
                character_id=char_id
            ).result()
            corp_id = char_resp["corporation_id"]

        corp_pub = fetch_corp_public(corp_id)
        corp_name = corp_pub.get("name") or f"Corporation {corp_id}"
        corp_ticker = corp_pub.get("ticker") or ""
        logo_url = f"https://images.evetech.net/corporations/{corp_id}/logo?size=128"

        corp, _ = Corporation.objects.get_or_create(
            corporation_id=corp_id,
            defaults={
                "name": corp_name,
                "ticker": corp_ticker,
                "logo_url": logo_url,
            },
        )

        changed_fields = []
        if corp.name != corp_name:
            corp.name = corp_name
            changed_fields.append("name")
        if corp.ticker != corp_ticker:
            corp.ticker = corp_ticker
            changed_fields.append("ticker")
        if corp.logo_url != logo_url:
            corp.logo_url = logo_url
            changed_fields.append("logo_url")
        if changed_fields:
            corp.save(update_fields=changed_fields)

        existing_unassigned = CorpTokenLink.objects.filter(
            corp=corp,
            character_id=char_id,
            user__isnull=True,
        ).first()

        if existing_unassigned:
            changed = False
            if existing_unassigned.user_id != request.user.id:
                existing_unassigned.user = request.user
                changed = True
            if existing_unassigned.character_name != char_name:
                existing_unassigned.character_name = char_name
                changed = True
            if changed:
                existing_unassigned.save()
        else:
            CorpTokenLink.objects.update_or_create(
                corp=corp,
                user=request.user,
                character_id=char_id,
                defaults={
                    "character_name": char_name,
                },
            )

        now = timezone.now()
        pull_month_for_corp.delay(corp.corporation_id, now.year, now.month)

        messages.success(
            request,
            _t("Linked token for %(corp)s. Initial sync queued.") % {
                "corp": corp.name,
            }
        )
    except Exception:
        logger.exception("connect_corp_token failed")
        messages.error(request, _t("Error while connecting corp token."))

    return redirect("rattingtax:dashboard")