from __future__ import annotations

from dataclasses import dataclass
from decimal import Decimal, ROUND_HALF_UP
from typing import Iterable

from django.db import transaction
from django.db.models import Max, Prefetch

from bookings.models import Booking
from invoicing.deletion import is_being_deleted, is_booking_id_being_deleted
from invoicing.models import Receipt, ReceiptLineItem
from structures.models import Structure
from structures.city_tax_service import calculate_booking_city_tax_total

from .models import BookingService

TWOPLACES = Decimal("0.01")


def quantize_money(value: Decimal | int | float | str) -> Decimal:
    return Decimal(str(value or 0)).quantize(TWOPLACES, rounding=ROUND_HALF_UP)


def calculate_net_from_gross(gross_amount: Decimal, vat_rate: Decimal) -> Decimal:
    gross_amount = quantize_money(gross_amount)
    vat_rate = Decimal(str(vat_rate or 0))
    if vat_rate <= 0:
        return gross_amount

    divisor = Decimal("1") + (vat_rate / Decimal("100"))
    return (gross_amount / divisor).quantize(TWOPLACES, rounding=ROUND_HALF_UP)


def calculate_vat_from_gross(gross_amount: Decimal, vat_rate: Decimal) -> Decimal:
    net_amount = calculate_net_from_gross(gross_amount, vat_rate)
    return quantize_money(gross_amount - net_amount)


@dataclass(frozen=True)
class CalculatedLineItem:
    line_type: str
    description: str
    vat_rate: Decimal
    gross_amount: Decimal
    net_amount: Decimal
    vat_amount: Decimal


@dataclass(frozen=True)
class ReceiptBreakdown:
    line_items: list[CalculatedLineItem]
    taxable_gross: Decimal
    taxable_net: Decimal
    taxable_vat: Decimal
    city_tax_amount: Decimal
    total_net: Decimal
    total_gross: Decimal


def get_receipt_number_components(structure: Structure) -> tuple[int, str]:
    Structure.objects.select_for_update().get(pk=structure.pk)
    next_sequence = (
        Receipt.objects.filter(structure=structure).aggregate(max_sequence=Max("sequence"))[
            "max_sequence"
        ]
        or 0
    ) + 1
    prefix = (structure.receipt_prefix or "REC").strip() or "REC"
    return next_sequence, f"{prefix}-{next_sequence:05d}"


def _normalize_decimal(value: Decimal | int | float | str | None) -> Decimal:
    return Decimal(str(value or 0))


def _get_accommodation_gross(booking: Booking, city_tax_amount: Decimal) -> Decimal:
    subtotal_amount = quantize_money(booking.subtotal)
    fallback_amount = quantize_money(quantize_money(booking.total_price) - city_tax_amount)

    if subtotal_amount > 0 or fallback_amount <= 0:
        return subtotal_amount
    return fallback_amount


def _replace_receipt_line_items(
    receipt: Receipt,
    line_items: Iterable[CalculatedLineItem],
) -> None:
    receipt.line_items.all().delete()
    ReceiptLineItem.objects.bulk_create(
        [
            ReceiptLineItem(
                receipt=receipt,
                line_type=line_item.line_type,
                description=line_item.description,
                vat_rate=line_item.vat_rate,
                gross_amount=line_item.gross_amount,
                net_amount=line_item.net_amount,
                vat_amount=line_item.vat_amount,
            )
            for line_item in line_items
        ]
    )


def build_receipt_breakdown(booking: Booking) -> ReceiptBreakdown:
    booking_services = list(booking.booking_services.all())
    if not booking_services:
        booking_services = list(
            BookingService.objects.filter(booking=booking).select_related("service")
        )

    city_tax_amount = quantize_money(calculate_booking_city_tax_total(booking))
    accommodation_gross = _get_accommodation_gross(booking, city_tax_amount)
    accommodation_vat_rate = Decimal(str(booking.structure.default_vat_rate or 0))
    accommodation_net = calculate_net_from_gross(accommodation_gross, accommodation_vat_rate)
    accommodation_vat = calculate_vat_from_gross(accommodation_gross, accommodation_vat_rate)

    line_items = [
        CalculatedLineItem(
            line_type=ReceiptLineItem.LineType.ACCOMMODATION,
            description="Accommodation",
            vat_rate=accommodation_vat_rate,
            gross_amount=accommodation_gross,
            net_amount=accommodation_net,
            vat_amount=accommodation_vat,
        )
    ]

    taxable_gross = accommodation_gross
    taxable_net = accommodation_net
    taxable_vat = accommodation_vat

    for booking_service in booking_services:
        gross_amount = quantize_money(booking_service.price)
        vat_rate = Decimal(str(booking_service.vat_rate or 0))
        net_amount = calculate_net_from_gross(gross_amount, vat_rate)
        vat_amount = calculate_vat_from_gross(gross_amount, vat_rate)

        line_items.append(
            CalculatedLineItem(
                line_type=ReceiptLineItem.LineType.SERVICE,
                description=booking_service.service.name,
                vat_rate=vat_rate,
                gross_amount=gross_amount,
                net_amount=net_amount,
                vat_amount=vat_amount,
            )
        )
        taxable_gross += gross_amount
        taxable_net += net_amount
        taxable_vat += vat_amount

    if city_tax_amount > 0:
        line_items.append(
            CalculatedLineItem(
                line_type=ReceiptLineItem.LineType.CITY_TAX,
                description="City Tax",
                vat_rate=Decimal("0"),
                gross_amount=city_tax_amount,
                net_amount=city_tax_amount,
                vat_amount=Decimal("0.00"),
            )
        )

    return ReceiptBreakdown(
        line_items=line_items,
        taxable_gross=quantize_money(taxable_gross),
        taxable_net=quantize_money(taxable_net),
        taxable_vat=quantize_money(taxable_vat),
        city_tax_amount=city_tax_amount,
        total_net=quantize_money(taxable_net + city_tax_amount),
        total_gross=quantize_money(taxable_gross + city_tax_amount),
    )


@transaction.atomic
def sync_receipt_for_booking(booking: Booking | int) -> Receipt | None:
    if booking is None:
        return None

    if isinstance(booking, Booking):
        if getattr(booking, "_is_being_deleted", False) or is_being_deleted(booking):
            return None
        if getattr(booking, "_skip_receipt_sync", False):
            return None
        if not booking.structure.invoicing_enabled:
            return None
        if not booking.is_checked_in:
            return None

    if isinstance(booking, Booking) and is_being_deleted(booking):
        return None

    booking_id = booking.id if isinstance(booking, Booking) else int(booking)
    if is_booking_id_being_deleted(booking_id):
        return None

    booking = (
        Booking.objects.select_related("structure")
        .prefetch_related(Prefetch("booking_services", queryset=BookingService.objects.select_related("service")))
        .filter(pk=booking_id)
        .first()
    )
    if booking is None or is_being_deleted(booking):
        return None

    if getattr(booking, "_skip_receipt_sync", False):
        return None

    if not booking.structure.invoicing_enabled:
        return None

    if not booking.is_checked_in:
        return None

    receipt = (
        Receipt.objects.select_for_update()
        .filter(reservation=booking)
        .first()
    )
    if receipt is not None and receipt.is_finalized:
        return receipt

    breakdown = build_receipt_breakdown(booking)

    if receipt is None:
        sequence, number = get_receipt_number_components(booking.structure)
        receipt = Receipt.objects.create(
            structure=booking.structure,
            reservation=booking,
            number=number,
            sequence=sequence,
            total_gross=breakdown.total_gross,
            total_net=breakdown.total_net,
            total_vat=breakdown.taxable_vat,
            city_tax_amount=breakdown.city_tax_amount,
        )
    else:
        receipt.total_gross = breakdown.total_gross
        receipt.total_net = breakdown.total_net
        receipt.total_vat = breakdown.taxable_vat
        receipt.city_tax_amount = breakdown.city_tax_amount
        receipt.save(
            update_fields=[
                "total_gross",
                "total_net",
                "total_vat",
                "city_tax_amount",
                "updated_at",
            ]
        )

    _replace_receipt_line_items(receipt, breakdown.line_items)
    return receipt


def sync_receipts_for_structure(structure: Structure | int) -> int:
    structure_id = structure.id if isinstance(structure, Structure) else int(structure)
    booking_ids = list(
        Booking.objects.filter(
            structure_id=structure_id,
            is_checked_in=True,
        ).values_list("id", flat=True)
    )
    synced_count = 0
    for booking_id in booking_ids:
        if sync_receipt_for_booking(booking_id) is not None:
            synced_count += 1
    return synced_count


@transaction.atomic
def delete_draft_receipts_for_structure(structure: Structure | int) -> int:
    structure_id = structure.id if isinstance(structure, Structure) else int(structure)
    draft_receipts = Receipt.objects.select_for_update().filter(
        structure_id=structure_id,
        is_finalized=False,
    )
    deleted_count = draft_receipts.count()
    draft_receipts.delete()
    return deleted_count


def handle_structure_invoicing_settings_change(
    structure: Structure,
    *,
    previous_invoicing_enabled: bool,
    previous_default_vat_rate: Decimal | int | float | str | None,
) -> None:
    current_invoicing_enabled = bool(structure.invoicing_enabled)
    previous_vat_rate = _normalize_decimal(previous_default_vat_rate)
    current_vat_rate = _normalize_decimal(structure.default_vat_rate)

    if previous_invoicing_enabled and not current_invoicing_enabled:
        delete_draft_receipts_for_structure(structure)
        return

    if current_invoicing_enabled and (
        not previous_invoicing_enabled or previous_vat_rate != current_vat_rate
    ):
        sync_receipts_for_structure(structure)
