from datetime import date, datetime, timezone
from decimal import Decimal
from uuid import UUID

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.orm import Session

from app.api.workspace import get_verified_current_user
from app.core.permissions import Permission
from app.db.session import get_db
from app.models.business import Business
from app.models.customer import Customer
from app.models.notification import Notification
from app.models.receivable import (
    DebtReminder,
    Receivable,
    ReceivableCollection,
)
from app.models.user import User
from app.schemas.receivable import (
    DebtReminderCreate,
    ReceivableCollectionCreate,
)
from app.services.audit import record_audit
from app.services.email_sender import send_debt_reminder_email
from app.services.receivables import (
    aging_bucket,
    collect,
    get_receivable,
    statement,
)
from app.services.rbac import require_permission


router = APIRouter(tags=["Receivables / Debt Book"])


def access(db, user, business):
    return require_permission(
        db,
        user,
        business,
        Permission.MANAGE_FINANCES,
    )


def run(db, fn):
    try:
        return fn()
    except LookupError as error:
        raise HTTPException(404, str(error))
    except ValueError as error:
        db.rollback()
        raise HTTPException(422, str(error))


@router.get(
    "/api/businesses/{business_id}/receivables/aging"
)
def aging(
    business_id: UUID,
    customer_id: UUID | None = None,
    user: User = Depends(get_verified_current_user),
    db: Session = Depends(get_db),
):
    access(db, user, business_id)

    query = select(Receivable).where(
        Receivable.business_id == business_id
    )

    if customer_id:
        query = query.where(
            Receivable.customer_id == customer_id
        )

    return [
        {
            "receivable": receivable,
            "aging_bucket": aging_bucket(receivable),
        }
        for receivable in db.scalars(
            query.order_by(
                Receivable.created_at.desc()
            )
        )
    ]


@router.get(
    "/api/businesses/{business_id}/receivables"
)
def listing(
    business_id: UUID,
    customer_id: UUID | None = None,
    status: str | None = None,
    overdue: bool | None = None,
    start_date: date | None = None,
    end_date: date | None = None,
    user: User = Depends(get_verified_current_user),
    db: Session = Depends(get_db),
):
    access(db, user, business_id)

    query = select(Receivable).where(
        Receivable.business_id == business_id
    )

    if customer_id:
        query = query.where(
            Receivable.customer_id == customer_id
        )

    if status:
        query = query.where(
            Receivable.status == status
        )

    if start_date:
        query = query.where(
            Receivable.created_at >= start_date
        )

    if end_date:
        query = query.where(
            Receivable.created_at
            < end_date.fromordinal(
                end_date.toordinal() + 1
            )
        )

    rows = list(
        db.scalars(
            query.order_by(
                Receivable.created_at.desc()
            )
        )
    )

    return [
        row
        for row in rows
        if overdue is None
        or (
            aging_bucket(row) != "CURRENT"
        )
        == overdue
    ]


# IMPORTANT: static reminder route comes before
# /receivables/{receivable_id}.
@router.get(
    "/api/businesses/{business_id}/receivables/reminders"
)
def reminder_history(
    business_id: UUID,
    customer_id: UUID | None = None,
    user: User = Depends(get_verified_current_user),
    db: Session = Depends(get_db),
):
    access(db, user, business_id)

    query = select(DebtReminder).where(
        DebtReminder.business_id == business_id
    )

    if customer_id:
        query = query.where(
            DebtReminder.customer_id == customer_id
        )

    return list(
        db.scalars(
            query.order_by(
                DebtReminder.created_at.desc()
            )
        )
    )


@router.get(
    "/api/businesses/{business_id}/receivables/{receivable_id}"
)
def detail(
    business_id: UUID,
    receivable_id: UUID,
    user: User = Depends(get_verified_current_user),
    db: Session = Depends(get_db),
):
    access(db, user, business_id)

    return run(
        db,
        lambda: get_receivable(
            db,
            business_id,
            receivable_id,
        ),
    )


@router.get(
    "/api/businesses/{business_id}/receivables/{receivable_id}/collections"
)
def collections(
    business_id: UUID,
    receivable_id: UUID,
    user: User = Depends(get_verified_current_user),
    db: Session = Depends(get_db),
):
    access(db, user, business_id)

    run(
        db,
        lambda: get_receivable(
            db,
            business_id,
            receivable_id,
        ),
    )

    return list(
        db.scalars(
            select(ReceivableCollection)
            .where(
                ReceivableCollection.receivable_id
                == receivable_id
            )
            .order_by(
                ReceivableCollection.collected_at
            )
        )
    )


@router.post(
    "/api/businesses/{business_id}/receivables/{receivable_id}/collections",
    status_code=201,
)
def create_collection(
    business_id: UUID,
    receivable_id: UUID,
    data: ReceivableCollectionCreate,
    user: User = Depends(get_verified_current_user),
    db: Session = Depends(get_db),
):
    access(db, user, business_id)

    return run(
        db,
        lambda: collect(
            db,
            business_id,
            receivable_id,
            user.id,
            data,
        )[0],
    )


@router.get(
    "/api/businesses/{business_id}/customers/{customer_id}/receivables"
)
def customer_summary(
    business_id: UUID,
    customer_id: UUID,
    include_paid: bool = True,
    user: User = Depends(get_verified_current_user),
    db: Session = Depends(get_db),
):
    access(db, user, business_id)

    customer = db.get(Customer, customer_id)

    if (
        not customer
        or customer.business_id != business_id
    ):
        raise HTTPException(
            404,
            "Customer not found.",
        )

    rows = list(
        db.scalars(
            select(Receivable)
            .where(
                Receivable.business_id == business_id,
                Receivable.customer_id == customer_id,
            )
            .order_by(
                Receivable.created_at.desc()
            )
        )
    )

    ids = [row.id for row in rows]

    collections = (
        []
        if not ids
        else list(
            db.scalars(
                select(ReceivableCollection)
                .where(
                    ReceivableCollection.receivable_id.in_(
                        ids
                    )
                )
                .order_by(
                    ReceivableCollection.collected_at.desc()
                )
            )
        )
    )

    return {
        "customer_id": customer_id,
        "total_outstanding": sum(
            (row.balance for row in rows),
            Decimal("0"),
        ),
        "open_receivables": [
            row
            for row in rows
            if row.status != "PAID"
        ],
        "paid_receivables": (
            [
                row
                for row in rows
                if row.status == "PAID"
            ]
            if include_paid
            else []
        ),
        "collections": collections,
    }


@router.get(
    "/api/businesses/{business_id}/customers/{customer_id}/statement"
)
def customer_statement(
    business_id: UUID,
    customer_id: UUID,
    user: User = Depends(get_verified_current_user),
    db: Session = Depends(get_db),
):
    access(db, user, business_id)

    customer = db.get(Customer, customer_id)

    if (
        not customer
        or customer.business_id != business_id
    ):
        raise HTTPException(
            404,
            "Customer not found.",
        )

    return statement(
        db,
        business_id,
        customer_id,
    )


@router.post(
    "/api/businesses/{business_id}/customers/{customer_id}/reminders",
    status_code=201,
)
def send_customer_reminder(
    business_id: UUID,
    customer_id: UUID,
    data: DebtReminderCreate,
    user: User = Depends(get_verified_current_user),
    db: Session = Depends(get_db),
):
    access(db, user, business_id)

    customer = db.get(Customer, customer_id)

    if (
        not customer
        or customer.business_id != business_id
    ):
        raise HTTPException(
            404,
            "Customer not found.",
        )

    if not customer.email:
        raise HTTPException(
            422,
            "This customer does not have an email address.",
        )

    open_items = list(
        db.scalars(
            select(Receivable).where(
                Receivable.business_id == business_id,
                Receivable.customer_id == customer_id,
                Receivable.balance > 0,
            )
        )
    )

    outstanding = sum(
        (item.balance for item in open_items),
        Decimal("0"),
    )

    if outstanding <= 0:
        raise HTTPException(
            422,
            "This customer has no outstanding debt.",
        )

    due_dates = [
        item.due_date
        for item in open_items
        if item.due_date is not None
    ]

    earliest_due = (
        min(due_dates)
        if due_dates
        else None
    )

    business = db.get(Business, business_id)

    reminder = DebtReminder(
        business_id=business_id,
        customer_id=customer_id,
        created_by=user.id,
        channel="EMAIL",
        recipient=customer.email,
        amount_outstanding=outstanding,
        message=data.message,
        status="PENDING",
    )

    db.add(reminder)
    db.flush()

    try:
        send_debt_reminder_email(
            customer.email,
            customer.name,
            business.name if business else "AAD Business",
            outstanding,
            earliest_due,
            data.message,
        )
    except Exception as error:
        reminder.status = "FAILED"
        reminder.error_code = type(error).__name__[:100]

        record_audit(
            db,
            business_id,
            "DEBT_REMINDER_FAILED",
            "customer",
            customer.id,
            user.id,
            None,
            {
                "customer_id": str(customer.id),
                "channel": "EMAIL",
                "amount_outstanding": str(outstanding),
                "error_code": reminder.error_code,
            },
        )

        db.commit()
        db.refresh(reminder)

        raise HTTPException(
            502,
            "Reminder email could not be sent. "
            "The failed attempt was recorded.",
        )

    reminder.status = "SENT"
    reminder.sent_at = datetime.now(timezone.utc)

    db.add(
        Notification(
            business_id=business_id,
            user_id=user.id,
            category="DEBT_REMINDER",
            title="Debt reminder sent",
            message=(
                f"Email reminder sent to {customer.name} "
                f"for NGN {outstanding:,.2f}."
            ),
            related_entity_type="customer",
            related_entity_id=customer.id,
        )
    )

    record_audit(
        db,
        business_id,
        "DEBT_REMINDER_SENT",
        "customer",
        customer.id,
        user.id,
        None,
        {
            "customer_id": str(customer.id),
            "channel": "EMAIL",
            "amount_outstanding": str(outstanding),
            "recipient": customer.email,
        },
    )

    db.commit()
    db.refresh(reminder)

    return reminder
