import logging
from utils.services import diff_day, current_date, get_month_days, next_month_due
from paymentlog.models import PaymentLog, CalculationDates
from tenant.models import  TenantRent, TenantRoom
from room.models import Room
from datetime import timedelta
from room.services import get_tenant_room_on_date, get_room_occupancy, get_active_tenants

logger = logging.getLogger(__name__)

def get_rent_log_instance(rent_info, date = None):
    """Use inside of a transaction. Create a food log instance based on the provided rent_info and date."""
    generation_date = current_date(date) if date is not None else current_date()
    tenant_rent = 0
    tenant_id = rent_info.tenant.id

    dates = CalculationDates.objects.select_for_update().get(tenant_id=tenant_id) # type: ignore

    tenant_rooms = TenantRoom.objects.filter( # type: ignore
        tenant_id=tenant_id,
        joined_on__gt=dates.last_rent_calculation,
        joined_on__lte=generation_date
    ).order_by('joined_on')

    start_calculation_from = None
    if tenant_rooms.exists():
        start_calculation_from = tenant_rooms.first().joined_on
    elif rent_info.tenant.check_in.day == dates.last_rent_calculation.day:
        start_calculation_from = dates.last_rent_calculation
    else:
        start_calculation_from = dates.last_rent_calculation + timedelta(days=1)

    current_iter_date = start_calculation_from
    last_iter_date = min(generation_date, rent_info.rent_due)
    room_numbers = []
    # Track sharing period with occupancy
    sharing_start_date = None
    current_monthly_price = None
    current_occupancy = None
    continuous_sharing_days = 0
    rent_share_during_sharing = 0

    while current_iter_date < last_iter_date:
        current_iter_room = get_tenant_room_on_date(tenant_id, current_iter_date)
        if current_iter_room:
            active_tenants = get_room_occupancy(current_iter_room.room.id, current_iter_date)
            price_per_day = (current_iter_room.room.price_per_month / current_iter_room.room.total_beds) / get_month_days(current_iter_date)

            # Track occupancy periods - regardless of number of tenants
            if sharing_start_date is None or current_occupancy != active_tenants or current_monthly_price != current_iter_room.room.price_per_month:
                sharing_start_date = current_iter_date
                current_monthly_price = current_iter_room.room.price_per_month
                current_occupancy = active_tenants
                continuous_sharing_days = 1  # Reset counter
            else:
                continuous_sharing_days += 1 # Increment counter for continuous sharing


            # Check for month completion only if:
            # 1. We're one day before rent_due
            # 2. Generation date matches rent_due
            if current_iter_date.day == (last_iter_date - timedelta(days=1)).day and continuous_sharing_days == get_month_days(sharing_start_date) and current_monthly_price is not None:
                rent_share_during_sharing = current_monthly_price / current_occupancy
                if current_iter_room.is_single_sharing:
                    rent_share_during_sharing = current_monthly_price
                else:
                    rent_share_during_sharing = current_monthly_price / current_iter_room.total_beds

                sharing_start_date = None
                current_monthly_price = None
                current_occupancy = None
                continuous_sharing_days = 0
                if current_iter_date.day == (last_iter_date - timedelta(days=1)).day:
                    tenant_rent = tenant_rent + rent_share_during_sharing
                rent_share_during_sharing = 0
            else:
                if current_iter_room.is_single_sharing:
                    rent_share_during_sharing += price_per_day * current_iter_room.total_beds  # Full room price
                else:
                    rent_share_during_sharing += price_per_day  # Shared rent
                # Always calculate daily rent unless we just handled a complete month
                if current_iter_date.day == (last_iter_date - timedelta(days=1)).day:
                    tenant_rent = tenant_rent + rent_share_during_sharing


            # Track room changes
            if len(room_numbers) == 0:
                room_numbers.append(current_iter_room.room.room_number)
            elif room_numbers[-1] != current_iter_room.room.room_number:
                room_numbers.append(current_iter_room.room.room_number)

        current_iter_date += timedelta(days=1)

    total_days = (last_iter_date - start_calculation_from).days + 1
    tenant_rent = round(tenant_rent, 2)

    # Update tracking dates
    dates.last_rent_calculation = last_iter_date - timedelta(days=1)
    dates.save()

    if generation_date >= rent_info.rent_due:
        rent_info.rent_due = next_month_due(rent_info.rent_due)
        rent_info.save()

    if tenant_rent > 0:
        return PaymentLog(
            amount=tenant_rent,
            tenant=rent_info.tenant,
            log_type="rent",
            room=rent_info.tenant.current_room.room,
            remark=f"""The log was created based on details:
                Total Days: {total_days},
                Room Numbers: {" --> ".join(room_numbers)}
            """,
            created_at=last_iter_date - timedelta(days=1),
            updated_at=last_iter_date - timedelta(days=1)
        )

    return None


def get_food_log_instance(tenant_rent, date=None):
    """Use inside of a transaction. Create a food log instance based on the provided tenant rent and date."""
    generation_date = current_date(date) if date is not None else current_date()
    tenant_id = tenant_rent.tenant_id
    room_id = tenant_rent.tenant.current_room.id
    dates = CalculationDates.objects.select_for_update().get(tenant_id=tenant_id) # type: ignore

    days_difference = diff_day(dates.last_food_calculation)
    total_payble = 0
    per_day_food_cost = 0

    if days_difference != 0:
        per_day_food_cost = tenant_rent.meal.price_per_month / 30
        total_payble = days_difference * per_day_food_cost

    # update date no matter what
    dates.last_food_calculation = generation_date
    dates.save()

    if total_payble > 0:
        return PaymentLog( # type: ignore
            amount= total_payble,
            tenant_id= tenant_id,
            log_type= "food",
            room_id= room_id,
            remark= f"""The log was created based on details:
                Meal Type: {tenant_rent.meal.name},
                Meal Price (per day): {round(per_day_food_cost, 2)},
                Total Days: {days_difference}
                """,
            created_at= generation_date,
            updated_at= generation_date,
        )

    return None

def get_tenants_with_rent_due(due_date=current_date()):
    return TenantRent.objects.filter( # type: ignore
        # Rent is due today or earlier
        rent_due__lte=due_date,
        # Join with TenantRoom to check for active rooms
        tenant__rooms__left_on__isnull=True,
        # Ensure room is active
        tenant__rooms__is_active=True
    ).select_related(
        'tenant'  # Optimize by pre-fetching tenant data
    ).distinct()

def generate_electricity_log_instances(room_id, current_electricity_reading, generation_date = None):
    """Use inside of a transaction. Generate electricity log instances for a room."""
    date_of_log = current_date(generation_date) if generation_date is not None else current_date()
    room = Room.objects.select_for_update().get(id=room_id) # type: ignore
    tenants = get_active_tenants(room_id)
    hostel = room.hostel
    payment_logs = []

    if len(tenants) > 0:
        total_electricity_units = int(current_electricity_reading) - room.last_electricity_reading
        total_electricity_amount = total_electricity_units * hostel.electricity_charge_per_unit
        electricity_per_tenant = total_electricity_amount / len(tenants)


        if(electricity_per_tenant > 0):
            for tenant in tenants:
                dates = CalculationDates.objects.select_for_update().get(tenant_id=tenant.id) # type: ignore
                payment_logs.append(PaymentLog(
                    amount=electricity_per_tenant,
                    tenant_id=tenant.id,
                    log_type="electricity",
                    room_id=room_id,
                    remark=f"""The log was created based on details:
                        Total Units: {current_electricity_reading } - {room.last_electricity_reading} = {total_electricity_units},
                        Charge per Unit: {hostel.electricity_charge_per_unit}
                        Total Amount: {total_electricity_amount},
                        Shared Between: {len(tenants)},
                    """,
                    created_at=date_of_log,
                    updated_at=date_of_log,
                ))
                dates.last_electricity_calculation = date_of_log
                dates.save()

    # no matter what always update the room reading to current meter reading.
    room.last_electricity_reading = current_electricity_reading
    room.save()

    return payment_logs
