"""RBAC-scoped project financial lookups exposed to the assistant as tool calls.

Separate module from apps.chatbot.tools (project/task listing) because
financial figures — allocations, expenses, grants — are a higher sensitivity
tier. Every function takes `user` first and applies two checks, same pattern
as apps.hr.ai_tools: the project must already be visible to the user (lead,
team member, or full project scope), and full financial detail is only
returned if the user is additionally a finance authority or the project lead
— otherwise the tool reports access is restricted rather than the figures.
"""
from django.db.models import Q

from apps.core.permissions import user_has_full_scope, user_is_finance_authority
from apps.projects.models import (
    CashAdvance,
    PaymentRequestForm,
    Project,
    ProjectFund,
    ProjectFundAllocation,
    ProjectFundCompliance,
    ProjectFundTranche,
    ProjectFinance,
)

PROJECT_FINANCE_TOOL_SCHEMAS = [
    {
        "type": "function",
        "function": {
            "name": "project_finance_summary",
            "description": "Get a project's income/expense ledger summary (totals by type, approval status). Totals are summed in the IDR reporting base and named *_idr — report them as Rupiah, never with a fund's native currency symbol. Requires finance access to the project.",
            "parameters": {
                "type": "object",
                "properties": {
                    "project": {"type": "string", "description": "Project id (UUID) or exact/partial name."},
                },
                "required": ["project"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "project_fund_allocations",
            "description": "List a project's fund budget-line allocations (budget item, quantity, unit cost, amount). `amount`/`unit_cost` are in `currency` (the fund's currency). Requires finance access to the project.",
            "parameters": {
                "type": "object",
                "properties": {
                    "project": {"type": "string", "description": "Project id (UUID) or exact/partial name."},
                },
                "required": ["project"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "project_expenses",
            "description": "List a project's cash advances and payment request forms (PRFs) with amounts and status. `amount` is in `currency`; `amount_idr` (cash advances only) is the same value converted to Rupiah — never pair `amount_idr` with `currency`. Requires finance access to the project.",
            "parameters": {
                "type": "object",
                "properties": {
                    "project": {"type": "string", "description": "Project id (UUID) or exact/partial name."},
                },
                "required": ["project"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "project_grants",
            "description": "List a project's funding sources/grants, including disbursement tranches and compliance/reporting obligations. `amount` is in `currency`; `amount_idr` is the same value converted to Rupiah — never pair `amount_idr` with `currency`. Requires finance access to the project.",
            "parameters": {
                "type": "object",
                "properties": {
                    "project": {"type": "string", "description": "Project id (UUID) or exact/partial name."},
                },
                "required": ["project"],
            },
        },
    },
]

RESTRICTED = {"error": "Financial detail for this project requires finance access or project lead — ask your project lead or finance team."}
NOT_FOUND = {"error": "Project not found or not visible to you."}


def _resolve_project(user, organization, project):
    qs = Project.objects.select_related("lead__user").filter(organization=organization)
    if not user_has_full_scope(user, "project", organization) and not user_is_finance_authority(user, organization):
        qs = qs.filter(Q(lead__user=user) | Q(team_members__user=user)).distinct()
    p = qs.filter(id=project).first() if _looks_like_uuid(project) else None
    if not p:
        p = qs.filter(name__icontains=project).first()
    return p


def _has_finance_access(user, organization, p):
    if user_is_finance_authority(user, organization):
        return True
    return bool(p.lead_id and p.lead.user_id == user.id)


def _looks_like_uuid(value: str) -> bool:
    import uuid
    try:
        uuid.UUID(str(value))
        return True
    except (ValueError, AttributeError):
        return False


def project_finance_summary(user, organization, project):
    p = _resolve_project(user, organization, project)
    if not p:
        return NOT_FOUND
    if not _has_finance_access(user, organization, p):
        return RESTRICTED
    entries = ProjectFinance.objects.filter(project=p)
    income = sum((e.amount_idr or e.amount) for e in entries.filter(type="income"))
    expense = sum((e.amount_idr or e.amount) for e in entries.filter(type="expense"))
    pending = entries.filter(approval_status="pending").count()
    # Totals mix currencies, so they are summed in the IDR reporting base —
    # the key names say so, otherwise the model labels them with a fund's
    # native currency and reports a ~20,000x wrong figure.
    return {
        "project": p.name,
        "total_income_idr": str(income),
        "total_expense_idr": str(expense),
        "entry_count": entries.count(),
        "pending_approval_count": pending,
    }


def project_fund_allocations(user, organization, project):
    p = _resolve_project(user, organization, project)
    if not p:
        return NOT_FOUND
    if not _has_finance_access(user, organization, p):
        return RESTRICTED
    allocations = ProjectFundAllocation.objects.select_related("fund", "budget_item").filter(fund__project=p)
    return [
        {
            "fund": a.fund.source,
            "budget_item": a.budget_item.name,
            "unit": a.unit,
            "quantity": str(a.quantity),
            "unit_cost": str(a.unit_cost),
            "amount": str(a.amount),
            "currency": a.fund.currency,
        }
        for a in allocations[:50]
    ]


def project_expenses(user, organization, project):
    p = _resolve_project(user, organization, project)
    if not p:
        return NOT_FOUND
    if not _has_finance_access(user, organization, p):
        return RESTRICTED
    advances = CashAdvance.objects.select_related("recipient__user").filter(project=p).order_by("-date_issued")[:25]
    prfs = PaymentRequestForm.objects.filter(project=p).order_by("-created_at")[:25]
    return {
        "cash_advances": [
            {
                "recipient": a.recipient.user.get_full_name() or a.recipient.user.email if a.recipient.user_id else None,
                "purpose": a.purpose,
                "amount": str(a.amount),
                "currency": a.currency,
                "amount_idr": str(a.amount_idr) if a.amount_idr is not None else None,
                "status": a.status,
                "date_issued": str(a.date_issued),
            }
            for a in advances
        ],
        "payment_requests": [
            {
                "number": f.number or str(f.id),
                "recipient_name": f.recipient_name,
                "amount": str(f.amount),
                "currency": f.currency,
                "status": f.status,
                "payment_date": str(f.payment_date) if f.payment_date else None,
            }
            for f in prfs
        ],
    }


def project_grants(user, organization, project):
    p = _resolve_project(user, organization, project)
    if not p:
        return NOT_FOUND
    if not _has_finance_access(user, organization, p):
        return RESTRICTED
    funds = ProjectFund.objects.select_related("donor").filter(project=p)
    tranches = ProjectFundTranche.objects.select_related("fund").filter(fund__project=p).order_by("expected_date")[:25]
    compliance = ProjectFundCompliance.objects.filter(fund__project=p).order_by("due_date")[:25]
    return {
        "funds": [
            {
                "source": f.donor.name if f.donor_id else f.source,
                "fund_type": f.fund_type,
                "amount": str(f.amount),
                "currency": f.currency,
                "amount_idr": str(f.amount_idr) if f.amount_idr is not None else None,
                "status": f.status,
            }
            for f in funds
        ],
        "tranches": [
            {
                "label": t.label,
                "amount": str(t.amount),
                "currency": t.fund.currency,
                "expected_date": str(t.expected_date) if t.expected_date else None,
                "status": t.status,
            }
            for t in tranches
        ],
        "compliance": [
            {
                "title": c.title,
                "kind": c.kind,
                "due_date": str(c.due_date) if c.due_date else None,
                "status": c.status,
            }
            for c in compliance
        ],
    }


PROJECT_FINANCE_DISPATCH = {
    "project_finance_summary": project_finance_summary,
    "project_fund_allocations": project_fund_allocations,
    "project_expenses": project_expenses,
    "project_grants": project_grants,
}

PROJECT_FINANCE_ALLOWED_ARGS = {
    "project_finance_summary": {"project": None},
    "project_fund_allocations": {"project": None},
    "project_expenses": {"project": None},
    "project_grants": {"project": None},
}

PROJECT_FINANCE_MAX_ARG_LEN = 200


def call_project_finance_tool(user, organization, name, arguments: dict):
    fn = PROJECT_FINANCE_DISPATCH.get(name)
    if not fn:
        return {"error": f"Unknown tool: {name}"}

    allowed = PROJECT_FINANCE_ALLOWED_ARGS[name]
    if not isinstance(arguments, dict):
        return {"error": "Invalid tool arguments."}

    clean = {}
    for key, value in arguments.items():
        if key not in allowed:
            continue  # drop unknown args instead of erroring — model may hallucinate extra keys
        if not isinstance(value, str):
            continue
        clean[key] = value[:PROJECT_FINANCE_MAX_ARG_LEN]

    return fn(user, organization, **clean)
