from __future__ import annotations
from datetime import datetime, timezone
from decimal import Decimal

from quart import (
    request, make_response,
    Blueprint, g, redirect, url_for, jsonify,
)




from .services.entries import (

    add_entry as svc_add_entry,
)
from shared.browser.app.authz import require_admin
from shared.browser.app.redis_cacher import clear_cache
from shared.sx.helpers import sx_response


from bp.calendar_entry.routes import register as register_calendar_entry


from models.calendars import CalendarSlot

from sqlalchemy import select


def calculate_entry_cost(slot: CalendarSlot, start_at: datetime, end_at: datetime) -> Decimal:
    """
    Calculate cost for an entry based on slot and time range.
    - Fixed slot: use slot cost
    - Flexible slot: prorate based on actual time vs slot time range
    """
    if not slot.cost:
        return Decimal('0')
    
    if not slot.flexible:
        # Fixed slot: full cost
        return Decimal(str(slot.cost))
    
    # Flexible slot: calculate ratio
    if not slot.time_end or not start_at or not end_at:
        return Decimal('0')
    
    # Calculate durations in minutes
    slot_start_minutes = slot.time_start.hour * 60 + slot.time_start.minute
    slot_end_minutes = slot.time_end.hour * 60 + slot.time_end.minute
    slot_duration = slot_end_minutes - slot_start_minutes
    
    actual_start_minutes = start_at.hour * 60 + start_at.minute
    actual_end_minutes = end_at.hour * 60 + end_at.minute
    actual_duration = actual_end_minutes - actual_start_minutes
    
    if slot_duration <= 0 or actual_duration <= 0:
        return Decimal('0')
    
    ratio = Decimal(actual_duration) / Decimal(slot_duration)
    return Decimal(str(slot.cost)) * ratio


def register():
    bp = Blueprint("calendar_entries", __name__, url_prefix='/entries')

    bp.register_blueprint(
        register_calendar_entry()
    )
    
    @bp.post("/")
    @clear_cache(tag="calendars", tag_scope="all")
    async def add_entry(year: int, month: int, day: int, **kwargs):
        form = await request.form

        def parse_time_to_dt(value: str | None, year: int, month: int, day: int):
            if not value:
                return None
            try:
                hour_str, minute_str = value.split(":", 1)
                hour = int(hour_str)
                minute = int(minute_str)
                return datetime(year, month, day, hour, minute, tzinfo=timezone.utc)
            except Exception:
                return None

        name = (form.get("name") or "").strip()
        start_at = parse_time_to_dt(form.get("start_time"), year, month, day)
        end_at   = parse_time_to_dt(form.get("end_time"),   year, month, day)

        # NEW: slot_id
        slot_id_raw = (form.get("slot_id") or "").strip()
        slot_id = int(slot_id_raw) if slot_id_raw else None

        # Ticket configuration
        ticket_price_str = (form.get("ticket_price") or "").strip()
        ticket_price = None
        if ticket_price_str:
            try:
                ticket_price = Decimal(ticket_price_str)
            except Exception:
                pass

        ticket_count_str = (form.get("ticket_count") or "").strip()
        ticket_count = None
        if ticket_count_str:
            try:
                ticket_count = int(ticket_count_str)
            except Exception:
                pass

        field_errors: dict[str, list[str]] = {}

        # Basic checks
        if not name:
            field_errors.setdefault("name", []).append("Please enter a name for the entry.")

        # Check slot first before validating times
        slot = None
        cost = Decimal('10')  # default cost
        
        if slot_id is not None:
            result = await g.s.execute(
                select(CalendarSlot).where(
                    CalendarSlot.id == slot_id,
                    CalendarSlot.calendar_id == g.calendar.id,
                    CalendarSlot.deleted_at.is_(None),
                )
            )
            slot = result.scalar_one_or_none()
            if slot is None:
                field_errors.setdefault("slot_id", []).append(
                    "Selected slot is no longer available."
                )
            else:
                # For inflexible slots, override the times with slot times
                if not slot.flexible:
                    # Replace start/end with slot times
                    start_at = datetime(year, month, day, 
                                       slot.time_start.hour, 
                                       slot.time_start.minute, 
                                       tzinfo=timezone.utc)
                    if slot.time_end:
                        end_at = datetime(year, month, day,
                                         slot.time_end.hour,
                                         slot.time_end.minute,
                                         tzinfo=timezone.utc)
                else:
                    # Flexible: validate times are within slot band
                    # Only validate if times were provided
                    if not start_at:
                        field_errors.setdefault("start_time", []).append("Please select a start time.")
                    if end_at is None:
                        field_errors.setdefault("end_time", []).append("Please select an end time.")
                    
                    if start_at and end_at:
                        s_time = start_at.timetz()
                        e_time = end_at.timetz()
                        slot_start = slot.time_start
                        slot_end = slot.time_end

                        if s_time.replace(tzinfo=None) < slot_start:
                            field_errors.setdefault("start_time", []).append(
                                f"Start time must be at or after {slot_start.strftime('%H:%M')}."
                            )
                        if slot_end is not None and e_time.replace(tzinfo=None) > slot_end:
                            field_errors.setdefault("end_time", []).append(
                                f"End time must be at or before {slot_end.strftime('%H:%M')}."
                            )
                
                # Calculate cost based on slot and times
                if start_at and end_at:
                    cost = calculate_entry_cost(slot, start_at, end_at)
        else:
            field_errors.setdefault("slot_id", []).append(
                "Please select a slot."
            )

        # Time ordering check (only if we have times)
        if start_at and end_at and end_at < start_at:
            field_errors.setdefault("end_time", []).append("End time must be after the start time.")

        if field_errors:
            return jsonify(
                {
                    "message": "Please fix the highlighted fields.",
                    "errors": field_errors,
                }
            ), 422

        # Pass slot_id and calculated cost to the service
        entry = await svc_add_entry(
            g.s,
            calendar_id=g.calendar.id,
            name=name,
            start_at=start_at,
            end_at=end_at,
            user_id=getattr(g, "user", None).id if getattr(g, "user", None) else None,
            session_id=None,
            slot_id=slot_id,
            cost=cost,  # Pass calculated cost
        )

        # Set ticket configuration
        entry.ticket_price = ticket_price
        entry.ticket_count = ticket_count

        # Commit so cross-service calls see the new entry
        await g.tx.commit()
        g.tx = await g.s.begin()

        from shared.infrastructure.cart_identity import current_cart_identity
        from shared.infrastructure.fragments import fetch_fragment
        ident = current_cart_identity()
        frag_params = {"oob": "1"}
        if ident["user_id"] is not None:
            frag_params["user_id"] = str(ident["user_id"])
        if ident["session_id"] is not None:
            frag_params["session_id"] = ident["session_id"]

        # Re-query day entries for the sx component
        from datetime import date as date_cls, timedelta
        from bp.calendar.services import get_visible_entries_for_period
        from quart import session as qsession

        period_start = datetime(year, month, day, tzinfo=timezone.utc)
        period_end = period_start + timedelta(days=1)
        user = getattr(g, "user", None)
        session_id = qsession.get("calendar_sid")

        visible = await get_visible_entries_for_period(
            sess=g.s,
            calendar_id=g.calendar.id,
            period_start=period_start,
            period_end=period_end,
            user=user,
            session_id=session_id,
        )

        # Query day slots for this weekday
        day_date = date_cls(year, month, day)
        weekday_attr = ["mon","tue","wed","thu","fri","sat","sun"][day_date.weekday()]
        stmt = select(CalendarSlot).where(
            CalendarSlot.calendar_id == g.calendar.id,
            getattr(CalendarSlot, weekday_attr) == True,
            CalendarSlot.deleted_at.is_(None),
        ).order_by(CalendarSlot.time_start.asc(), CalendarSlot.id.asc())
        result = await g.s.execute(stmt)
        day_slots = list(result.scalars())

        styles = getattr(g, "styles", None) or {}
        ctx = {
            "calendar": g.calendar,
            "day_entries": visible.merged_entries,
            "day": day,
            "month": month,
            "year": year,
            "hx_select_search": "#main-panel",
            "styles": styles,
        }

        from sxc.pages.renders import render_day_main_panel
        html = render_day_main_panel(ctx)
        mini_html = await fetch_fragment("cart", "cart-mini", params=frag_params, required=False)
        return sx_response(html + (mini_html or ""))
    
    @bp.get("/add/")
    async def add_form(day: int, month: int, year: int, **kwargs):
        from datetime import date as date_cls
        from sqlalchemy import select as sa_select
        from models.calendars import CalendarSlot as _CS

        day_date = date_cls(year, month, day)
        weekday_attr = ["mon","tue","wed","thu","fri","sat","sun"][day_date.weekday()]
        stmt = sa_select(_CS).where(
            _CS.calendar_id == g.calendar.id,
            getattr(_CS, weekday_attr) == True,
            _CS.deleted_at.is_(None),
        ).order_by(_CS.time_start.asc(), _CS.id.asc())
        result = await g.s.execute(stmt)
        day_slots = list(result.scalars())

        from sxc.pages.entries import render_entry_add_form
        return sx_response(render_entry_add_form(g.calendar, day, month, year, day_slots))

    @bp.get("/add-button/")
    async def add_button(day: int, month: int, year: int, **kwargs):
        from sxc.pages.entries import render_entry_add_button
        return sx_response(render_entry_add_button(g.calendar, day, month, year))



    return bp
