from __future__ import annotations

from decimal import Decimal
from typing import Any
from urllib.parse import quote

from sqlalchemy import or_, select
from sqlalchemy.orm import Session, selectinload

from app.modules.billing.models import BillingInvoice, BillingInvoiceLine, BillingPayment
from app.modules.billing.services import build_invoice_print_context, is_cashfree_ready, is_payumoney_ready, money

CLIENT_VISIBLE_INVOICE_STATUSES = {"ISSUED", "PARTLY_PAID", "PAID", "OVERDUE"}


def _client_ids(client_row: Any) -> tuple[int, int]:
    """Return (tenant_id, client_id) from dict/row/model style client payload."""
    if isinstance(client_row, dict):
        return int(client_row.get("tenant_id") or 0), int(client_row.get("id") or 0)
    return int(getattr(client_row, "tenant_id", 0) or 0), int(getattr(client_row, "id", 0) or 0)


def list_client_portal_invoices(db: Session, client_row: Any, *, q: str = "", include_paid: bool = True, financial_year: str | None = None) -> list[BillingInvoice]:
    tenant_id, client_id = _client_ids(client_row)
    stmt = (
        select(BillingInvoice)
        .options(
            selectinload(BillingInvoice.lines).selectinload(BillingInvoiceLine.service),
            selectinload(BillingInvoice.payments),
        )
        .where(
            BillingInvoice.tenant_id == tenant_id,
            BillingInvoice.client_id == client_id,
            BillingInvoice.status.in_(CLIENT_VISIBLE_INVOICE_STATUSES),
        )
    )
    if not include_paid:
        stmt = stmt.where(BillingInvoice.status != "PAID")
    if financial_year and financial_year.upper() != "ALL":
        stmt = stmt.where(BillingInvoice.financial_year == financial_year)
    if q.strip():
        term = f"%{q.strip()}%"
        stmt = stmt.where(or_(BillingInvoice.invoice_no.ilike(term), BillingInvoice.invoice_title.ilike(term)))
    return db.execute(stmt.order_by(BillingInvoice.invoice_date.desc(), BillingInvoice.id.desc())).scalars().unique().all()


def get_client_portal_invoice(db: Session, client_row: Any, invoice_id: int, *, financial_year: str | None = None) -> BillingInvoice | None:
    tenant_id, client_id = _client_ids(client_row)
    stmt = (
        select(BillingInvoice)
        .options(
            selectinload(BillingInvoice.client),
            selectinload(BillingInvoice.lines).selectinload(BillingInvoiceLine.service),
            selectinload(BillingInvoice.payments),
        )
        .where(
            BillingInvoice.id == invoice_id,
            BillingInvoice.tenant_id == tenant_id,
            BillingInvoice.client_id == client_id,
            BillingInvoice.status.in_(CLIENT_VISIBLE_INVOICE_STATUSES),
        )
    )
    if financial_year and financial_year.upper() != "ALL":
        stmt = stmt.where(BillingInvoice.financial_year == financial_year)
    return db.execute(stmt).scalars().unique().one_or_none()


def get_client_portal_payment(db: Session, client_row: Any, payment_id: int, *, financial_year: str | None = None) -> BillingPayment | None:
    tenant_id, client_id = _client_ids(client_row)
    stmt = (
        select(BillingPayment)
        .options(
            selectinload(BillingPayment.invoice).selectinload(BillingInvoice.lines),
            selectinload(BillingPayment.client),
        )
        .where(
            BillingPayment.id == payment_id,
            BillingPayment.tenant_id == tenant_id,
            BillingPayment.client_id == client_id,
            BillingPayment.status == "RECEIVED",
        )
    )
    if financial_year and financial_year.upper() != "ALL":
        stmt = stmt.where(BillingPayment.financial_year == financial_year)
    return db.execute(stmt).scalars().unique().one_or_none()


def build_client_billing_summary(db: Session, client_row: Any, *, financial_year: str | None = None) -> dict[str, Any]:
    invoices = list_client_portal_invoices(db, client_row, include_paid=True, financial_year=financial_year)
    open_invoices = [row for row in invoices if row.status in {"ISSUED", "PARTLY_PAID", "OVERDUE"} and money(row.balance_amount) > Decimal("0.00")]
    paid_invoices = [row for row in invoices if row.status == "PAID"]
    outstanding = sum((money(row.balance_amount) for row in open_invoices), Decimal("0.00"))
    latest_invoice = invoices[0] if invoices else None
    latest_due_invoice = open_invoices[0] if open_invoices else None
    return {
        "billing_invoices": invoices,
        "billing_open_invoices": open_invoices,
        "billing_paid_invoices": paid_invoices,
        "billing_outstanding_amount": money(outstanding),
        "billing_latest_invoice": latest_invoice,
        "billing_latest_due_invoice": latest_due_invoice,
        "billing_open_count": len(open_invoices),
        "billing_paid_count": len(paid_invoices),
        "billing_total_count": len(invoices),
    }


def build_client_payment_context(db: Session, invoice: BillingInvoice) -> dict[str, Any]:
    invoice_ctx = build_invoice_print_context(db, invoice)
    settings = invoice_ctx.get("settings")
    amount_due = money(invoice.balance_amount)
    firm_name = invoice_ctx.get("firm_name") or "Audit Firm"
    upi_id = getattr(settings, "upi_id", None) if settings else None
    upi_link = None
    if upi_id and amount_due > Decimal("0.00"):
        upi_link = (
            "upi://pay?"
            f"pa={quote(str(upi_id))}"
            f"&pn={quote(str(firm_name))}"
            f"&am={quote(str(amount_due))}"
            "&cu=INR"
            f"&tn={quote('Invoice ' + str(invoice.invoice_no))}"
        )
    return {
        "invoice_ctx": invoice_ctx,
        "amount_due": amount_due,
        "upi_link": upi_link,
        "upi_id": upi_id,
        "bank_name": invoice_ctx.get("bank_name"),
        "bank_account_name": invoice_ctx.get("bank_account_name"),
        "bank_account_number": invoice_ctx.get("bank_account_number"),
        "bank_ifsc": invoice_ctx.get("bank_ifsc"),
        "payment_instructions": getattr(settings, "bank_details", None) if settings else None,
        "payumoney_enabled": is_payumoney_ready(settings),
        "payumoney_mode": getattr(settings, "payumoney_mode", "TEST") if settings else "TEST",
        "cashfree_enabled": is_cashfree_ready(settings),
        "cashfree_mode": getattr(settings, "cashfree_mode", "TEST") if settings else "TEST",
    }
