from datetime import date as _date, datetime as _datetime
from decimal import Decimal, InvalidOperation

from django import template
from django.utils.formats import number_format
from django.utils.html import escape, format_html
from django.utils.safestring import mark_safe

register = template.Library()


APPLICATION_BADGES = {
    "draft": ("neutral", "Draft"),
    "submitted": ("primary", "Submitted"),
    "under_review": ("primary", "Under review"),
    "shortlisted": ("amber", "Shortlisted"),
    "approved": ("primary", "Approved"),
    "awarded": ("green", "Awarded"),
    "rejected": ("red", "Not selected"),
    "ineligible": ("red", "Not eligible"),
    "rejected_ineligible": ("red", "Rejected — ineligible"),
    "withdrawn": ("neutral", "Withdrawn"),
    "withdrawn_no_response": ("neutral", "Withdrawn (no response)"),
    "expired_draft": ("neutral", "Draft expired"),
}

AWARD_BADGES = {
    "draft": ("neutral", "Draft"),
    "agreement_pending": ("amber", "Agreement pending"),
    "active": ("green", "Active"),
    "ready_for_closeout": ("amber", "Ready for close-out"),
    "closeout_in_progress": ("primary", "Close-out in progress"),
    "closed": ("neutral", "Closed"),
    "cancelled": ("red", "Cancelled"),
}

CALL_BADGES = {
    "draft": ("neutral", "Draft"),
    "published": ("primary", "Published"),
    "closed": ("neutral", "Closed"),
    "awarding": ("amber", "Awarding"),
    "completed": ("neutral", "Completed"),
    "withdrawn": ("red", "Withdrawn"),
}

AMENDMENT_BADGES = {
    "requested": ("amber", "Pending approval"),
    "approved": ("green", "Approved"),
    "rejected": ("red", "Rejected"),
}

CANCELLATION_BADGES = {
    "requested": ("amber", "Requested"),
    "director_review": ("primary", "Director review"),
    "approved": ("red", "Cancellation approved"),
    "rejected": ("neutral", "Rejected"),
    "withdrawn": ("neutral", "Withdrawn"),
}

COHORT_ADDENDUM_BADGES = {
    "requested": ("amber", "Requested"),
    "director_review": ("primary", "Director review"),
    "approved": ("green", "Approved"),
    "rejected": ("red", "Rejected"),
    "withdrawn": ("neutral", "Withdrawn"),
}

COHORT_BADGES = {
    "planned": ("neutral", "Planned"),
    "active": ("green", "Active"),
    "completed": ("primary", "Completed"),
}

AGREEMENT_BADGES = {
    True: ("green", "Accepted"),
    False: ("amber", "Pending"),
}

REPORT_BADGES = {
    "submitted": ("amber", "Awaiting decision"),
    "accepted": ("green", "Accepted"),
    "queries_raised": ("amber", "Queries raised"),
    "revision_required": ("red", "Revision required"),
}

TRANCHE_BADGES = {
    "planned": ("neutral", "Planned"),
    "requested": ("amber", "Requested"),
    "approved": ("primary", "Approved"),
    "disbursed": ("primary", "Disbursed"),
    "paid": ("green", "Paid"),
    "reconciled": ("green", "Reconciled"),
    "held": ("amber", "Held"),
    "cancelled": ("red", "Cancelled"),
}

PROJECT_BADGES = {
    "planned": ("neutral", "Planned"),
    "active": ("green", "Active"),
    "at_risk": ("amber", "At risk"),
    "completed": ("neutral", "Completed"),
    "on_hold": ("amber", "On hold"),
    "not_started": ("neutral", "Not started"),
    "cancelled": ("red", "Cancelled"),
    "closed": ("neutral", "Closed"),
}

MOU_BADGES = {
    "draft": ("neutral", "Draft"),
    "active": ("green", "Active"),
    "expired": ("neutral", "Expired"),
    "superseded": ("neutral", "Superseded"),
    "terminated": ("red", "Terminated"),
    "renewed": ("green", "Renewed"),
}

DISBURSEMENT_BADGES = {
    "draft": ("neutral", "Draft"),
    "submitted": ("amber", "Submitted"),
    "approved": ("primary", "Approved"),
    "rejected": ("red", "Rejected"),
    "paid": ("green", "Paid"),
    "cancelled": ("red", "Cancelled"),
}

FUNDING_BADGES = {
    "draft": ("neutral", "Draft"),
    "pending_approval": ("amber", "Pending approval"),
    "draft_revision_required": ("red", "Revision required"),
    "approved": ("green", "Approved"),
    "closed": ("neutral", "Closed"),
}

MILESTONE_BADGES = {
    "planned": ("neutral", "Planned"),
    "pending": ("neutral", "Pending"),
    "submitted": ("amber", "Submitted"),
    "under_review": ("primary", "Under review"),
    "accepted": ("green", "Accepted"),
    "revision_required": ("red", "Revision required"),
    "overdue": ("amber", "Overdue"),
}

STIPEND_BADGES = {
    "pending": ("amber", "Pending"),
    "paid": ("green", "Paid"),
    "cancelled": ("red", "Cancelled"),
}

PAYMENT_BATCH_BADGES = {
    "draft": ("neutral", "Draft"),
    "locked": ("amber", "Locked"),
    "executed": ("green", "Executed"),
    "cancelled": ("red", "Cancelled"),
    "reversed": ("red", "Reversed"),
}

BUDGET_BADGES = {
    "draft": ("neutral", "Draft"),
    "submitted": ("amber", "Submitted"),
    "approved": ("primary", "Approved"),
    "finalized": ("green", "Finalized"),
}

BUDGET_REVISION_BADGES = {
    "requested": ("amber", "Pending approval"),
    "approved": ("green", "Approved"),
    "rejected": ("red", "Rejected"),
}

PAYMENT_TX_BADGES = {
    "scheduled": ("neutral", "Scheduled"),
    "executed": ("green", "Executed"),
    "rescheduled": ("amber", "Rescheduled"),
    "failed": ("red", "Failed"),
    "reversed": ("red", "Reversed"),
}

RECONCILIATION_BADGES = {
    "pending": ("amber", "Pending"),
    "reconciled": ("green", "Reconciled"),
    "discrepancy": ("red", "Discrepancy"),
}

SCHOLAR_BADGES = {
    "enrolled":     ("primary", "Enrolled"),
    "active":       ("green",   "Active"),
    "paused":       ("amber",   "Paused studies"),
    "deferred":     ("amber",   "Deferred"),
    "discontinued": ("red",     "Discontinued"),
    "withdrawn":    ("neutral", "Withdrawn"),
    "completed":    ("green",   "Completed"),
}

INTERVIEW_BADGES = {
    "scheduled": ("amber", "Scheduled"),
    "completed": ("green", "Completed"),
    "cancelled": ("red", "Cancelled"),
    "no_show": ("red", "No-show"),
}

VERIFICATION_BADGES = {
    "pending": ("amber", "Pending"),
    "verified": ("green", "Verified"),
    "flagged": ("amber", "Flagged"),
    "rejected": ("red", "Rejected"),
}

PERIODIC_NARRATIVE_REPORT_BADGES = {
    "draft": ("neutral", "Draft"),
    "submitted": ("amber", "Submitted"),
    "accepted": ("green", "Accepted"),
    "revision_required": ("red", "Revision required"),
}

_TABLES = {
    "application": APPLICATION_BADGES,
    "award": AWARD_BADGES,
    "call": CALL_BADGES,
    "amendment": AMENDMENT_BADGES,
    "cancellation": CANCELLATION_BADGES,
    "cohort_addendum": COHORT_ADDENDUM_BADGES,
    "cohort": COHORT_BADGES,
    "report": REPORT_BADGES,
    "tranche": TRANCHE_BADGES,
    "project": PROJECT_BADGES,
    "mou": MOU_BADGES,
    "disbursement": DISBURSEMENT_BADGES,
    "milestone": MILESTONE_BADGES,
    "funding": FUNDING_BADGES,
    "stipend": STIPEND_BADGES,
    "payment_batch": PAYMENT_BATCH_BADGES,
    "budget": BUDGET_BADGES,
    "budget_revision": BUDGET_REVISION_BADGES,
    "payment_tx": PAYMENT_TX_BADGES,
    "reconciliation": RECONCILIATION_BADGES,
    "scholar": SCHOLAR_BADGES,
    "interview": INTERVIEW_BADGES,
    "verification": VERIFICATION_BADGES,
    "periodic_narrative_report": PERIODIC_NARRATIVE_REPORT_BADGES,
}


@register.simple_tag
def rims_status_badge(obj, kind):
    """Render a consistent status badge for any RIMS grants object.

    Usage:
        {% load rims_tags %}
        {% rims_status_badge application 'application' %}
        {% rims_status_badge award 'award' %}
        {% rims_status_badge amendment 'amendment' %}

    For awards with `is_overdue=True`, an Overdue badge is returned regardless
    of the underlying status — so callers don't need to branch.
    """
    if kind == "award" and getattr(obj, "is_overdue", False):
        return mark_safe('<span class="badge badge-amber">Overdue</span>')

    if kind == "agreement":
        variant, label = AGREEMENT_BADGES.get(
            bool(getattr(obj, "accepted", obj)), ("neutral", "Unknown")
        )
        return mark_safe(
            f'<span class="badge badge-{escape(variant)}">{escape(label)}</span>'
        )

    status = getattr(obj, "status", obj)
    table = _TABLES.get(kind, {})
    variant, label = table.get(
        status,
        ("neutral", str(getattr(obj, "get_status_display", lambda: status)()).title()),
    )
    return mark_safe(
        f'<span class="badge badge-{escape(variant)}">{escape(label)}</span>'
    )


@register.simple_tag
def rims_status_dot(status_or_obj, kind):
    """Render a coloured status dot — smaller indicator for list rows / tabs.

    Pairs with tab-btn__dot for showing "has pending items" state on a tab label.
    """
    status = getattr(status_or_obj, "status", status_or_obj)
    table = _TABLES.get(kind, {})
    variant, _ = table.get(status, ("neutral", ""))
    color_map = {
        "neutral": "bg-stone-400",
        "primary": "bg-ruforum-primary",
        "green": "bg-emerald-500",
        "red": "bg-red-500",
        "amber": "bg-amber-500",
    }
    cls = color_map.get(variant, "bg-stone-400")
    return mark_safe(
        f'<span class="inline-block h-1.5 w-1.5 rounded-full {cls}" aria-hidden="true"></span>'
    )


# ---------------------------------------------------------------------------
# §9 Finance confidence signals — amount, dual-currency, data footer.
# Three rendering primitives that give finance surfaces the peripheral signals
# (integer-weighted typography, FX rate provenance, data-as-of footer) a
# development-bank UI needs to feel trustworthy.
# ---------------------------------------------------------------------------


def _coerce_decimal(value) -> Decimal | None:
    if value is None or value == "":
        return None
    if isinstance(value, Decimal):
        return value
    try:
        return Decimal(str(value))
    except (InvalidOperation, ValueError, TypeError):
        return None


@register.simple_tag
def rims_amount(value, currency=None, decimals=2):
    """Split-typography currency amount.

    Renders `1,234.56 USD` with the integer part weighted slightly heavier
    than the decimal tail and the currency code as a small uppercase tracking
    chip. Eye lands on the integer first — the figure that actually matters.

    Usage:
        {% load rims_tags %}
        {% rims_amount disbursement.amount disbursement.budget.currency %}
    """
    amount = _coerce_decimal(value)
    if amount is None:
        amount = Decimal("0")
    decimals = max(0, int(decimals or 0))
    quantizer = Decimal(1).scaleb(-decimals) if decimals else Decimal(1)
    amount = amount.quantize(quantizer)
    integer_part = int(amount)
    integer_text = number_format(integer_part, force_grouping=True)
    if decimals:
        # `amount - integer_part` keeps the sign of `amount` for negative values
        # (-1234.56 → -.56 isn't a real string, so we render the absolute fraction).
        fraction_text = f".{abs(amount - integer_part):.{decimals}f}".split(".", 1)[-1]
        decimal_html = format_html(
            '<span class="rims-amount__dec">.{}</span>', fraction_text
        )
    else:
        decimal_html = ""
    ccy = (currency or "").strip()
    ccy_html = (
        format_html('<span class="rims-amount__ccy">{}</span>', ccy.upper())
        if ccy
        else ""
    )
    return format_html(
        '<span class="rims-amount"><span class="rims-amount__int">{}</span>{}{}</span>',
        integer_text,
        decimal_html,
        ccy_html,
    )


def _resolve_as_of(as_of):
    if as_of is None:
        return None
    if isinstance(as_of, _datetime):
        return as_of.date()
    if isinstance(as_of, _date):
        return as_of
    return None


@register.simple_tag
def rims_amount_dual(value, from_currency, to_currency=None, as_of=None):
    """Dual-currency amount with the rate inline.

    Renders the original amount, an arrow, the converted amount, and the
    `@ rate (source, date)` footnote on a second line. When no rate is on
    file for the requested pair / date, falls back to the original amount
    plus a muted "FX unavailable" note so the page still renders.

    Usage:
        {% rims_amount_dual disbursement.amount disbursement.budget.currency "USD" disbursement.created_at %}
    """
    from apps.rims.finance.models import ExchangeRate
    from apps.rims.finance.services import MissingExchangeRateError, convert
    from django.conf import settings

    amount = _coerce_decimal(value)
    if amount is None:
        amount = Decimal("0")
    src = (from_currency or "").strip().upper()
    target = (to_currency or getattr(settings, "DEFAULT_CURRENCY", "USD") or "USD").strip().upper()

    primary_html = rims_amount(amount, src)
    if not src or src == target:
        return primary_html

    as_of_date = _resolve_as_of(as_of)
    try:
        converted = convert(amount, src, target, as_of=as_of_date)
    except MissingExchangeRateError:
        note = format_html(
            '<span class="rims-amount__fx rims-amount__fx--missing">FX rate unavailable for {} → {}</span>',
            src,
            target,
        )
        return mark_safe(
            f'<span class="rims-amount-dual">{primary_html}<span class="rims-amount__sep" aria-hidden="true">·</span>{note}</span>'
        )

    rate_qs = (
        ExchangeRate.objects.filter(from_currency=src, to_currency=target)
        .order_by("-effective_date", "-pk")
    )
    if as_of_date is not None:
        rate_qs = rate_qs.filter(effective_date__lte=as_of_date)
    rate_row = rate_qs.first()
    if rate_row is None:
        rate_row = (
            ExchangeRate.objects.filter(from_currency=target, to_currency=src)
            .order_by("-effective_date", "-pk")
            .first()
        )

    converted_html = rims_amount(converted, target)
    if rate_row is not None:
        source_label = (rate_row.source or "").strip()
        rate_text = f"@ 1 {rate_row.from_currency} = {rate_row.rate.normalize():f} {rate_row.to_currency}"
        meta_bits = [rate_text, rate_row.effective_date.strftime("%d %b %Y")]
        if source_label:
            meta_bits.append(source_label)
        rate_note = format_html(
            '<span class="rims-amount__fx">{}</span>',
            " · ".join(meta_bits),
        )
    else:
        rate_note = format_html('<span class="rims-amount__fx">at current rate</span>')

    return mark_safe(
        '<span class="rims-amount-dual">'
        f'{primary_html}'
        '<span class="rims-amount__arrow" aria-hidden="true">→</span>'
        f'{converted_html}'
        f'{rate_note}'
        '</span>'
    )


@register.simple_tag
def rims_data_footer(as_of=None, source="", fx_refreshed=None):
    """Page-level provenance footer.

    A finance officer's trust in a number is built from peripheral signals.
    This footer carries the data-as-of timestamp, the source-of-truth label,
    and (when supplied) the date FX rates were last refreshed.

    Usage:
        {% rims_data_footer as_of=as_of source="DisbursementRequest live data" fx_refreshed=fx_refreshed %}
    """
    bits: list[str] = []
    if as_of is not None:
        if isinstance(as_of, _datetime):
            stamp = as_of.strftime("%d %b %Y, %H:%M")
            bits.append(f"As of {stamp} UTC")
        elif isinstance(as_of, _date):
            bits.append(f"As of {as_of.strftime('%d %b %Y')}")
    source = (source or "").strip()
    if source:
        bits.append(escape(source))
    if fx_refreshed is not None:
        if isinstance(fx_refreshed, _datetime):
            bits.append(f"FX rates last refreshed {fx_refreshed.strftime('%d %b %Y')}")
        elif isinstance(fx_refreshed, _date):
            bits.append(f"FX rates last refreshed {fx_refreshed.strftime('%d %b %Y')}")
    if not bits:
        return ""
    inner = " · ".join(bits)
    return mark_safe(f'<p class="rims-data-footer">{inner}</p>')
