"""Lets the AI assistant complete an entire order — delivery location (the
customer's already-saved profile pin), delivery date, nearest available
picker, and order creation — directly from a chat conversation, for
customers who'd rather just tell the assistant "schedule it" than click
through the Cart/Checkout pages themselves.

This deliberately reuses the exact same picker-availability, pricing, and
order-creation logic as the real checkout flow (market/views.py) — it's a
chat-triggered shortcut through that pipeline, not a second one that could
drift out of sync. It only ever assigns a B2C picker automatically (never
manual picker browsing in chat, and no B2B/truck flow — a business order
still goes through the real checkout, since truck selection depends on
order weight in a way worth seeing on a full page); it only works once the
customer already has a saved delivery location on file (chat has no map to
drop a new pin on)."""

import datetime

from accounts.supabase_client import get_client

from .customer_location import NAMED_LOCATION_KINDS, get_named_location, get_saved_location
from .geo import get_multi_stop_route, get_route, haversine_km, split_pickup_delivery_distance
from .markets import get_market, get_picker_origin_coords
from .payment_methods import build_whatsapp_proof_link, get_active_payment_methods
from .picker_availability import get_order_counts_for_date_batch
from .picker_ranking import rank_pickers
from .pricing import calculate_delivery_fee, calculate_package_fee, calculate_picking_fee
from .ratings import get_picker_rating_summary_batch, get_platform_rating_summary
from .scheduling import next_delivery_dates
from .site_settings import get_max_orders_per_day, get_multi_stop_pickup_enabled
from .supabase_orders import create_order_and_items


def describe_available_delivery_days():
    """The next 3 real Monday/Wednesday/Saturday delivery dates, plus
    "quick" — given to the AI so it only ever offers choices that actually
    exist, never an invented date."""
    return [{'date': d.isoformat(), 'label': d.strftime('%A, %d %b')} for d in next_delivery_dates(3)]


def _resolve_delivery_date(delivery_choice):
    """delivery_choice is either 'quick' or one of the ISO dates handed to
    the AI via describe_available_delivery_days(). Falls back to the
    nearest upcoming delivery day for anything else, rather than failing
    the whole action over a malformed date from the model."""
    if delivery_choice == 'quick':
        from .scheduling import quick_delivery_available

        if quick_delivery_available():
            return datetime.date.today().isoformat(), True
        # Quick delivery's daily cutoff has passed (see
        # scheduling.quick_delivery_available) — same graceful fallback as a
        # malformed date, rather than failing the request outright.
    try:
        from .site_settings import get_delivery_weekdays

        d = datetime.date.fromisoformat(str(delivery_choice))
        if d >= datetime.date.today() and d.weekday() in get_delivery_weekdays():
            return d.isoformat(), False
    except (TypeError, ValueError):
        pass
    fallback = next_delivery_dates(1)[0]
    return fallback.isoformat(), False


def find_nearest_available_picker(lat, lng, delivery_date, is_quick, required_market_ids=None):
    """Same fair, rating-aware ranking as the real checkout flow (proximity
    + how loaded a picker already is that day + rating — see
    market/picker_ranking.py), minus the same-route lead (that's specific to
    a picker already mid-delivery near the new destination, tracked
    separately in market/views.py and not worth the extra queries for this
    simpler chat shortcut). required_market_ids, when it has exactly one
    entry, restricts candidates to pickers actually registered at that
    market — the cart has an item unique to it (see
    market/views.py::_required_market_ids). Two or more entries (multi-stop
    pickup, admin opt-in) deliberately doesn't filter — any available
    picker can visit every required market as a stop, same as the real
    checkout flow. Returns (best_picker_dict_or_None, distance_km_or_None)."""
    required_market_ids = required_market_ids or []
    client = get_client()
    max_orders_per_day = get_max_orders_per_day()
    query = (
        client.table('pickker_picker_profiles')
        .select('*')
        .eq('is_approved', True)
        .eq('picker_segment', 'b2c')
    )
    if len(required_market_ids) == 1:
        query = query.eq('market_id', required_market_ids[0])
    profiles_resp = query.execute()
    profiles = [
        p for p in profiles_resp.data
        if p.get('market_id') or p.get('location_lat') is not None
    ]

    # Same region lock as the real checkout flow — see
    # market/views.py::_checkout_picker_b2c for the full reasoning.
    from .regions import resolve_region_for_location
    delivery_region_id = resolve_region_for_location(lat, lng)
    if delivery_region_id:
        profiles = [p for p in profiles if not p.get('region_id') or p['region_id'] == delivery_region_id]

    picker_ids = [p['user_id'] for p in profiles]
    users_by_id = {}
    if picker_ids:
        users_resp = client.table('pickker_users').select('id, first_name, last_name').in_('id', picker_ids).eq('is_active', True).execute()
        users_by_id = {u['id']: u for u in users_resp.data}

    # Batched — one query each for every candidate's daily order count and
    # ratings, instead of one query per candidate.
    order_counts = get_order_counts_for_date_batch(picker_ids, delivery_date)
    rating_summaries = get_picker_rating_summary_batch(picker_ids)
    platform_avg_rating = get_platform_rating_summary().get('average')

    candidates = []
    for p in profiles:
        user = users_by_id.get(p['user_id'])
        if not user:
            continue
        today_count = order_counts.get(p['user_id'], 0)
        if today_count >= max_orders_per_day:
            continue
        # Anchored to the picker's registered market, never wherever they
        # physically are mid-delivery — same reasoning as checkout_picker.
        origin_lat, origin_lng = get_picker_origin_coords(p)
        if origin_lat is None:
            continue
        candidates.append({
            'user_id': p['user_id'],
            'name': f"{user['first_name']} {user['last_name']}".strip() or 'Picker',
            'lat': origin_lat,
            'lng': origin_lng,
            'vehicle_type': p.get('vehicle_type') or 'car',
            'distance_km': round(haversine_km(lat, lng, origin_lat, origin_lng), 2),
            'today_count': today_count,
        })

    ranked = rank_pickers(candidates, rating_summaries, platform_avg_rating)
    if not ranked:
        return None, None
    best = ranked[0]
    return best, best['distance_km']


def schedule_order_from_chat(customer_id, account_type, delivery_choice, location_choice=None):
    """Attempts to fully place an order from the chat assistant.

    location_choice, when given ('home' or 'business'), resolves against the
    customer's explicitly-registered named location instead of the default
    "last used at checkout" saved location — lets a customer just say
    "deliver to my home" rather than dropping a fresh pin every time.

    Returns a dict on success with everything the chat widget needs to
    render an inline confirmation card (order id, assigned picker + their
    vehicle, both locations for a small route map, date, and the fee
    breakdown), or {'error': 'no_location' | 'empty_cart' | 'no_picker' |
    'named_location_not_set', 'location_choice': ...} on failure so the
    assistant can explain what's missing instead of the request just
    silently failing."""
    # Imported here (not at module top) to avoid a circular import — views.py
    # already imports this module, and these helpers live in views.py.
    from .views import (
        _cart_items_and_subtotal, _distinct_items_by_category, _has_generic_items, _required_market_ids,
        _required_vendor_ties, _total_quantity, _total_weight_kg, _vendor_order_eligibility,
    )

    if account_type == 'b2b':
        return {'error': 'b2b_not_supported'}

    if location_choice in NAMED_LOCATION_KINDS:
        location = get_named_location(customer_id, location_choice)
        if not location:
            return {'error': 'named_location_not_set', 'location_choice': location_choice}
    else:
        location = get_saved_location(customer_id)
        if not location:
            return {'error': 'no_location'}

    items, subtotal = _cart_items_and_subtotal(customer_id, account_type)
    if not items:
        return {'error': 'empty_cart'}

    vendor_ties = _required_vendor_ties(items)
    vendor = vendor_type = vendor_id = None
    if vendor_ties:
        if len(vendor_ties) > 1:
            # A genuine pick-from-a-short-list choice (which vendor's fleet
            # to use), not a fits-in-one-shot auto-assignment — steps aside
            # to the real checkout, same as the real web flow's own
            # msg_mixed_vendor_cart guard (market/views.py::checkout_provider).
            return {'error': 'vendor_fleet_not_supported_in_chat'}
        vendor_type, vendor_id = next(iter(vendor_ties))
        if _required_market_ids(items):
            # Cart also mixes in a market-tied item — real checkout has a
            # known, pre-existing routing-priority ambiguity for this exact
            # combination (see _route_for_candidate in market/views.py,
            # where the market-suggestion branch silently wins over the
            # vendor waypoint); chat deliberately doesn't replicate that
            # ambiguity, it just punts to the real checkout.
            return {'error': 'vendor_fleet_not_supported_in_chat'}
        from .vendors import get_vendor
        vendor = get_vendor(vendor_type, vendor_id)
        zone, _min_kg, eligible = _vendor_order_eligibility(vendor, {'lat': location['lat'], 'lng': location['lng']}, _total_weight_kg(items))
        if not (zone == 'close' and eligible):
            # Everything else this phase's economics cover (far zone, out
            # of range, below the zone's minimum) still needs the real
            # checkout's hard-block/shop-fallback UI, not a silent chat
            # auto-assignment — same "genuine choice, not fits-in-one-shot"
            # reasoning as the multi-vendor case above.
            return {'error': 'vendor_fleet_not_supported_in_chat'}

    required_market_ids = list(_required_market_ids(items))
    if len(required_market_ids) >= 2 and get_multi_stop_pickup_enabled():
        # Admin has opted into multi-stop pickups — same as the real
        # checkout flow, a picker can visit every required market as a stop
        # on the one trip instead of this being blocked, however many there
        # are.
        pass
    elif len(required_market_ids) > 1:
        # Two or more items each unique to a different market, and
        # multi-stop pickups aren't turned on — no single picker can fetch
        # all of them.
        return {'error': 'mixed_market_cart'}

    delivery_date, is_quick = _resolve_delivery_date(delivery_choice)
    picker, straight_line_km = find_nearest_available_picker(location['lat'], location['lng'], delivery_date, is_quick, required_market_ids)
    if not picker:
        return {'error': 'no_picker'}

    multi_stop_markets = []
    suggested_market = None
    if len(required_market_ids) >= 2:
        multi_stop_markets = [m for m in (get_market(mid) for mid in required_market_ids) if m]
        waypoints = [(float(m['lat']), float(m['lng'])) for m in multi_stop_markets]
        route = get_multi_stop_route(picker['lat'], picker['lng'], waypoints, location['lat'], location['lng'])
    elif len(required_market_ids) == 1 and _has_generic_items(items):
        # Mixed order, same as the real checkout flow — suggest the market
        # nearest this picker as an extra stop for the cart's generic items.
        from .markets import get_nearest_market
        nearest_market = get_nearest_market(picker['lat'], picker['lng'])
        if nearest_market and nearest_market['id'] != required_market_ids[0]:
            suggested_market = nearest_market
            tied_market = get_market(required_market_ids[0])
            if tied_market:
                multi_stop_markets = [tied_market, nearest_market]
            waypoints = [(float(tied_market['lat']), float(tied_market['lng'])), (float(nearest_market['lat']), float(nearest_market['lng']))]
            route = get_multi_stop_route(picker['lat'], picker['lng'], waypoints, location['lat'], location['lng'])
        else:
            route = get_route(picker['lat'], picker['lng'], location['lat'], location['lng'])
    elif vendor is not None:
        # Close-zone warehouse/industry order — the picker still has to
        # swing by the vendor to collect the goods before heading to the
        # customer, same waypoint pattern the real web checkout's
        # _route_for_candidate uses for the identical case.
        waypoints = [(float(vendor['lat']), float(vendor['lng']))]
        route = get_multi_stop_route(picker['lat'], picker['lng'], waypoints, location['lat'], location['lng'])
    else:
        route = get_route(picker['lat'], picker['lng'], location['lat'], location['lng'])
    distance_km = route['distance_km'] if route else round(straight_line_km, 2)
    pickup_distance_km, delivery_distance_km = split_pickup_delivery_distance(route) if route else (None, None)
    from .truck_types import get_per_km_rate_for_picker
    delivery_fee = calculate_delivery_fee(distance_km, is_quick=is_quick, per_km_rate=get_per_km_rate_for_picker(picker.get('user_id')))
    picking_fee = calculate_picking_fee(_total_weight_kg(items), _distinct_items_by_category(items))
    package_fee = calculate_package_fee(_total_weight_kg(items), _total_quantity(items))

    order, _ = create_order_and_items(
        customer_id=customer_id,
        picker_id=picker['user_id'],
        address=location['address'],
        lat=location['lat'],
        lng=location['lng'],
        picker_lat=picker['lat'],
        picker_lng=picker['lng'],
        distance_km=distance_km,
        pickup_distance_km=pickup_distance_km,
        delivery_distance_km=delivery_distance_km,
        items=items,
        subtotal=subtotal,
        delivery_fee=delivery_fee,
        picking_fee=picking_fee,
        package_fee=package_fee,
        delivery_date=delivery_date,
        delivery_time_slot='Quick Delivery (ASAP)' if is_quick else 'Anytime that day',
        is_quick=is_quick,
    )

    if suggested_market:
        from .shop_payments import record_suggested_market
        record_suggested_market(order['id'], suggested_market['id'])

    if vendor is not None:
        from .stock import decrement_vendor_stock_for_order
        failed_product = decrement_vendor_stock_for_order(order['id'], items, vendor_type, vendor_id)
        if failed_product:
            return {'error': 'out_of_stock', 'product_name': failed_product.get('display_name') or failed_product['name']}

    from . import cart as cart_utils
    from .checkout_state import clear_checkout_state
    from .order_emails import notify_order_placed

    cart_utils.clear_cart(customer_id)
    clear_checkout_state(customer_id)
    notify_order_placed(order)

    # Every order starts out pending_payment (see supabase_orders.py), so the
    # chat's order-scheduled card can show the same "how to pay" step the
    # real order-detail page shows, right there inline, instead of leaving
    # the customer to click through and find it themselves.
    customer_resp = get_client().table('pickker_users').select('first_name, last_name, phone_number, email').eq('id', customer_id).execute()
    customer = customer_resp.data[0] if customer_resp.data else {}
    customer_name = f"{customer.get('first_name', '')} {customer.get('last_name', '')}".strip()
    total = subtotal + delivery_fee + picking_fee + package_fee

    return {
        'order_id': order['id'],
        'picker_name': picker['name'],
        'picker_vehicle_type': picker['vehicle_type'],
        'picker_lat': picker['lat'],
        'picker_lng': picker['lng'],
        'customer_lat': location['lat'],
        'customer_lng': location['lng'],
        'delivery_date': delivery_date,
        'is_quick': is_quick,
        'distance_km': distance_km,
        'pickup_distance_km': pickup_distance_km,
        'delivery_distance_km': delivery_distance_km,
        'delivery_fee': float(delivery_fee),
        'picking_fee': float(picking_fee),
        'package_fee': float(package_fee),
        'subtotal': float(subtotal),
        'total': float(total),
        'multi_stop_markets': [m['name'] for m in multi_stop_markets] if multi_stop_markets else None,
        'payment_methods': get_active_payment_methods(),
        'whatsapp_link': build_whatsapp_proof_link(
            order['id'], total, customer_name, customer.get('phone_number', ''), customer.get('email', ''),
        ),
    }
