from decimal import Decimal

from django.db import transaction
from django.db.models import Sum
from django.utils import timezone

from apps.distributions.models import ProjectLedgerLine
from apps.notifications.models import NotificationKind
from apps.notifications.services import notify_project_investors
from apps.sales.models import ProjectSale, PropertyPayment, SaleStage, UnitStatus


STAGE_ORDER = [SaleStage.BOOKING, SaleStage.CONTRACTED, SaleStage.COLLECTED, SaleStage.RECOGNIZED]

UNIT_FOR_STAGE = {
    SaleStage.BOOKING: UnitStatus.BOOKED,
    SaleStage.CONTRACTED: UnitStatus.CONTRACTED,
    SaleStage.COLLECTED: UnitStatus.CONTRACTED,
    SaleStage.RECOGNIZED: UnitStatus.SOLD,
}


def _coerce_amount(amount, fallback=None):
    if amount is None or (isinstance(amount, str) and not amount.strip()):
        amount = fallback
    if amount is None:
        raise ValueError("Contracted amount is required.")
    if isinstance(amount, Decimal):
        return amount
    text = str(amount).strip().replace(",", "").replace("৳", "").replace(" ", "")
    try:
        return Decimal(text)
    except Exception as exc:
        raise ValueError("Contracted amount must be a valid number.") from exc


@transaction.atomic
def create_sale(project, unit, customer, amount, user, notes=""):
    sale = ProjectSale.objects.create(
        project=project,
        unit=unit,
        customer=customer,
        stage=SaleStage.BOOKING,
        contracted_amount=_coerce_amount(amount, fallback=unit.list_price),
        booked_at=timezone.now(),
        notes=notes,
        created_by=user,
        currency=unit.currency,
    )
    unit.status = UnitStatus.BOOKED
    unit.save(update_fields=["status", "updated_at"])
    notify_project_investors(
        project,
        "Flat/plot booked",
        f"{project.name} — {unit.unit_code} has been booked. This is a booking, not recognized revenue or distributable profit.",
        kind=NotificationKind.MILESTONE,
    )
    return sale


@transaction.atomic
def advance_sale(sale, user, stage=None):
    if stage is None:
        idx = STAGE_ORDER.index(sale.stage)
        if idx >= len(STAGE_ORDER) - 1:
            return sale
        stage = STAGE_ORDER[idx + 1]
    sale.stage = stage
    now = timezone.now()
    if stage == SaleStage.CONTRACTED:
        sale.contracted_at = now
        notify_project_investors(
            sale.project,
            "Flat/plot sold",
            f"{sale.project.name} — {sale.unit.unit_code} is now a contracted sale of ৳ {sale.contracted_amount}.",
            kind=NotificationKind.MILESTONE,
        )
    elif stage == SaleStage.COLLECTED:
        sale.collected_at = now
        notify_project_investors(
            sale.project,
            "Customer payment received",
            f"{sale.project.name} — collections updated for {sale.unit.unit_code}. Collected ৳ {sale.collected_amount}.",
            kind=NotificationKind.TRANSACTION,
        )
    elif stage == SaleStage.RECOGNIZED:
        sale.recognized_at = now
        ProjectLedgerLine.objects.create(
            project=sale.project,
            kind=ProjectLedgerLine.Kind.REVENUE,
            category="Unit sale",
            amount=sale.contracted_amount,
            occurred_on=now.date(),
            recognized=True,
            notes=f"{sale.unit.unit_code} recognized",
        )
        notify_project_investors(
            sale.project,
            "Project revenue updated",
            f"{sale.project.name} — {sale.unit.unit_code} revenue has been recognized in the project ledger.",
            kind=NotificationKind.TRANSACTION,
        )
    sale.unit.status = UNIT_FOR_STAGE[stage]
    sale.unit.save(update_fields=["status", "updated_at"])
    sale.save()
    return sale


@transaction.atomic
def record_payment(sale, amount, user, occurred_on=None, reference=""):
    amount = Decimal(amount)
    payment = PropertyPayment.objects.create(
        sale=sale,
        amount=amount,
        occurred_on=occurred_on or timezone.now().date(),
        reference=reference,
        posted_by=user,
        currency=sale.currency,
    )
    sale.collected_amount = (sale.collected_amount or Decimal("0")) + amount
    sale.save(update_fields=["collected_amount", "updated_at"])
    if sale.stage == SaleStage.BOOKING:
        advance_sale(sale, user, SaleStage.CONTRACTED)
    if sale.collected_amount >= sale.contracted_amount and sale.stage != SaleStage.RECOGNIZED:
        advance_sale(sale, user, SaleStage.COLLECTED)
    notify_project_investors(
        sale.project,
        "Customer payment received",
        f"{sale.project.name} — a payment of ৳ {amount} was recorded for {sale.unit.unit_code}.",
        kind=NotificationKind.TRANSACTION,
    )
    return payment


def sales_dashboard(project):
    units = project.property_units.all()
    sales = project.sales.all()
    counts = {
        "total_units": units.count(),
        "available": units.filter(status=UnitStatus.AVAILABLE).count(),
        "reserved": units.filter(status=UnitStatus.RESERVED).count(),
        "booked": units.filter(status=UnitStatus.BOOKED).count(),
        "sold": units.filter(status__in=[UnitStatus.CONTRACTED, UnitStatus.SOLD]).count(),
    }
    aggregates = sales.aggregate(
        contracted=Sum("contracted_amount"),
        collected=Sum("collected_amount"),
    )
    contracted = aggregates["contracted"] or Decimal("0")
    collected = aggregates["collected"] or Decimal("0")
    return {
        "project": project.slug,
        "project_name": project.name,
        **counts,
        "total_contracted_sales": str(contracted),
        "amount_collected": str(collected),
        "outstanding": str(contracted - collected),
    }
