from decimal import Decimal, ROUND_HALF_UP

from django.utils import timezone


MONEY_STEP = Decimal("0.01")

# Sri Lanka land pricing normalization:
# 1 acre = 160 perches.
ACRE_TO_PERCH = Decimal("160")

# Floor-area pricing normalization:
# 1 square meter ≈ 10.7639 square feet.
SQM_TO_SQFT = Decimal("10.7639")


def money(value):
    """
    Safely round money to 2 decimal places.
    """
    if value is None:
        return None

    return Decimal(value).quantize(
        MONEY_STEP,
        rounding=ROUND_HALF_UP
    )


def resolve_requested_visits(selection):
    """
    Determine the practical monthly visit count.

    Requested visits entered by the owner takes priority where valid.
    Otherwise, use a fallback based on selected frequency.
    """

    if selection.frequency == 'one_time':
        return 0

    if selection.requested_visits_per_month is not None:
        if selection.requested_visits_per_month > 0:
            return selection.requested_visits_per_month

    fallback_map = {
        'monthly_once': 1,
        'monthly_twice': 2,
        'weekly_once': 4,
        'weekly_twice': 8,
        'custom': 0,
    }

    return fallback_map.get(selection.frequency, 1)


def normalize_land_to_perches(managed_property):
    """
    Convert land size into perch-equivalent units for calculator use.
    """

    if managed_property.land_size is None:
        return None

    land_size = Decimal(managed_property.land_size)

    if managed_property.land_size_unit == 'acres':
        return land_size * ACRE_TO_PERCH

    return land_size


def normalize_floor_area_to_sqft(managed_property):
    """
    Convert building floor area into square-feet equivalent units for calculator use.
    """

    if managed_property.total_floor_area is None:
        return None

    floor_area = Decimal(managed_property.total_floor_area)

    if managed_property.floor_area_unit == 'sqm':
        return floor_area * SQM_TO_SQFT

    return floor_area


def total_room_count(managed_property):
    """
    Combined room count used by room-based pricing methods.
    """

    return (
        managed_property.bedrooms
        + managed_property.bathrooms
        + managed_property.kitchens
        + managed_property.living_rooms
        + managed_property.other_rooms
    )


def calculate_selection_amount(managed_property, selection):
    """
    Calculate a monthly estimate for a single selected service.

    Returns a dictionary:
    {
        'status': 'calculated' | 'quote_required' | 'missing_input' | 'not_monthly',
        'amount': Decimal or None,
        'basis': description string,
        'note': explanatory note
    }
    """

    service = selection.service_type

    if not service.is_active:
        return {
            'status': 'quote_required',
            'amount': None,
            'basis': '',
            'note': 'This service is currently inactive and requires staff review.'
        }

    if service.requires_staff_quote:
        return {
            'status': 'quote_required',
            'amount': None,
            'basis': '',
            'note': 'This service requires a custom staff quotation.'
        }

    if not service.is_calculator_enabled:
        return {
            'status': 'quote_required',
            'amount': None,
            'basis': '',
            'note': 'This service is not enabled for automatic calculator pricing.'
        }

    if service.calculation_method == 'custom_quote':
        return {
            'status': 'quote_required',
            'amount': None,
            'basis': '',
            'note': 'This service is configured for custom quotation.'
        }

    # One-time services should not be added to a monthly recurring total.
    if selection.frequency == 'one_time':
        return {
            'status': 'not_monthly',
            'amount': None,
            'basis': '',
            'note': 'One-time / as-needed service. Not included in recurring monthly total.'
        }

    base_price = Decimal(service.base_monthly_price_usd or 0)
    unit_price = Decimal(service.price_per_unit_usd or 0)
    minimum_price = Decimal(service.minimum_monthly_price_usd or 0)

    amount = Decimal("0.00")
    basis = ""

    # 1. Flat monthly price
    if service.calculation_method == 'flat_monthly':
        amount = base_price
        basis = f"Flat monthly service fee: ${money(base_price)}"

    # 2. Per visit
    elif service.calculation_method == 'per_visit':
        visits = resolve_requested_visits(selection)

        if visits <= 0:
            return {
                'status': 'missing_input',
                'amount': None,
                'basis': '',
                'note': 'A monthly visit count is required to calculate this service.'
            }

        amount = base_price + (unit_price * Decimal(visits))
        basis = (
            f"Base ${money(base_price)} + "
            f"${money(unit_price)} × {visits} visit(s)"
        )

    # 3. Based on land size
    elif service.calculation_method == 'per_land_unit':
        land_perches = normalize_land_to_perches(managed_property)

        if land_perches is None or land_perches <= 0:
            return {
                'status': 'missing_input',
                'amount': None,
                'basis': '',
                'note': 'Land size is required to calculate this service.'
            }

        amount = base_price + (unit_price * land_perches)
        basis = (
            f"Base ${money(base_price)} + "
            f"${money(unit_price)} × {money(land_perches)} perch-equivalent unit(s)"
        )

    # 4. Based on floor area
    elif service.calculation_method == 'per_floor_area':
        floor_sqft = normalize_floor_area_to_sqft(managed_property)

        if floor_sqft is None or floor_sqft <= 0:
            return {
                'status': 'missing_input',
                'amount': None,
                'basis': '',
                'note': 'Total floor area is required to calculate this service.'
            }

        amount = base_price + (unit_price * floor_sqft)
        basis = (
            f"Base ${money(base_price)} + "
            f"${money(unit_price)} × {money(floor_sqft)} sq.ft-equivalent unit(s)"
        )

    # 5. Based on number of buildings
    elif service.calculation_method == 'per_building':
        building_count = managed_property.number_of_buildings

        if building_count <= 0:
            return {
                'status': 'missing_input',
                'amount': None,
                'basis': '',
                'note': 'Number of buildings is required to calculate this service.'
            }

        amount = base_price + (unit_price * Decimal(building_count))
        basis = (
            f"Base ${money(base_price)} + "
            f"${money(unit_price)} × {building_count} building(s)"
        )

    # 6. Based on room count
    elif service.calculation_method == 'per_room':
        room_count = total_room_count(managed_property)

        if room_count <= 0:
            return {
                'status': 'missing_input',
                'amount': None,
                'basis': '',
                'note': 'Room count is required to calculate this service.'
            }

        amount = base_price + (unit_price * Decimal(room_count))
        basis = (
            f"Base ${money(base_price)} + "
            f"${money(unit_price)} × {room_count} room(s)"
        )

    else:
        return {
            'status': 'quote_required',
            'amount': None,
            'basis': '',
            'note': 'No calculator rule is configured for this service.'
        }

    # Apply service minimum if configured
    if minimum_price > 0 and amount < minimum_price:
        amount = minimum_price
        basis = f"{basis} → Minimum service price applied: ${money(minimum_price)}"

    return {
        'status': 'calculated',
        'amount': money(amount),
        'basis': basis,
        'note': 'Included in provisional monthly estimate.'
    }


def calculate_property_maintenance_estimate(managed_property, persist=False):
    """
    Calculate a full property-level monthly estimate.

    If persist=True:
    - store estimated amount per PropertyServiceSelection
    - store latest property monthly estimate total
    - store estimate timestamp
    - store whether custom-quote services exist
    """

    selections = (
        managed_property.service_selections
        .select_related('service_type')
        .all()
    )

    calculated_items = []
    quote_required_items = []
    missing_input_items = []
    non_monthly_items = []

    total_monthly_estimate = Decimal("0.00")

    for selection in selections:
        result = calculate_selection_amount(managed_property, selection)

        item = {
            'selection': selection,
            'service': selection.service_type,
            'status': result['status'],
            'amount': result['amount'],
            'basis': result['basis'],
            'note': result['note'],
        }

        if result['status'] == 'calculated':
            calculated_items.append(item)
            total_monthly_estimate += result['amount']

            if persist:
                selection.estimated_monthly_amount_usd = result['amount']
                selection.save(
                    update_fields=[
                        'estimated_monthly_amount_usd',
                        'updated_at',
                    ]
                )

        elif result['status'] == 'quote_required':
            quote_required_items.append(item)

            if persist:
                selection.estimated_monthly_amount_usd = None
                selection.save(
                    update_fields=[
                        'estimated_monthly_amount_usd',
                        'updated_at',
                    ]
                )

        elif result['status'] == 'missing_input':
            missing_input_items.append(item)

            if persist:
                selection.estimated_monthly_amount_usd = None
                selection.save(
                    update_fields=[
                        'estimated_monthly_amount_usd',
                        'updated_at',
                    ]
                )

        elif result['status'] == 'not_monthly':
            non_monthly_items.append(item)

            if persist:
                selection.estimated_monthly_amount_usd = None
                selection.save(
                    update_fields=[
                        'estimated_monthly_amount_usd',
                        'updated_at',
                    ]
                )

    total_monthly_estimate = money(total_monthly_estimate)

    has_custom_quote_services = bool(
        quote_required_items or
        missing_input_items or
        non_monthly_items
    )

    generated_at = timezone.now()

    if persist:
        managed_property.estimated_monthly_total_usd = total_monthly_estimate
        managed_property.estimate_generated_at = generated_at
        managed_property.has_custom_quote_services = has_custom_quote_services

        managed_property.save(
            update_fields=[
                'estimated_monthly_total_usd',
                'estimate_generated_at',
                'has_custom_quote_services',
            ]
        )

    return {
        'property': managed_property,
        'selected_service_count': selections.count(),
        'calculated_service_count': len(calculated_items),
        'custom_or_pending_count': (
            len(quote_required_items)
            + len(missing_input_items)
            + len(non_monthly_items)
        ),
        'calculated_items': calculated_items,
        'quote_required_items': quote_required_items,
        'missing_input_items': missing_input_items,
        'non_monthly_items': non_monthly_items,
        'total_monthly_estimate': total_monthly_estimate,
        'has_custom_quote_services': has_custom_quote_services,
        'generated_at': generated_at,
    }