"""Generic dispatch over the three orderable source types — Market (see
market/markets.py, unchanged), Warehouse, and Industry. Only Warehouse and
Industry are handled here: Market keeps its own dedicated module and its
own order-tie tables (pickker_order_markets/pickker_shop_payments)
completely unchanged, since those are live and already proven. This module
is the generalized, additive parallel for the two new vendor types —
pickker_products.vendor_type/vendor_id is the generic column that ties a
product to any of the three (kept in sync with market_id for market-tied
products at the point products are saved, see accounts/views.py::_product_form)."""

from datetime import datetime, timezone

from django.core.cache import cache

from accounts.supabase_client import get_client

_CACHE_TTL = 300

_TABLE_BY_TYPE = {
    'warehouse': 'pickker_warehouses',
    'industry': 'pickker_industries',
}

VENDOR_TYPES = ('warehouse', 'industry')


def _cache_key(vendor_type):
    return f'pickker_{vendor_type}s_all'


def _invalidate(vendor_type):
    cache.delete(_cache_key(vendor_type))


def get_all_vendors(vendor_type):
    """All rows (active and inactive) of the given non-market vendor type,
    cached the same way markets.get_all_markets() is."""
    table = _TABLE_BY_TYPE.get(vendor_type)
    if not table:
        return []
    rows = cache.get(_cache_key(vendor_type))
    if rows is None:
        resp = get_client().table(table).select('*').order('region').order('name').execute()
        rows = resp.data
        cache.set(_cache_key(vendor_type), rows, _CACHE_TTL)
    return rows


def get_active_vendors(vendor_type):
    return [v for v in get_all_vendors(vendor_type) if v['is_active']]


def get_vendors_owned_by_company(company_id):
    """Every Warehouse/Industry this company owns, tagged with its type —
    used by the company's own fleet dashboard (self-service registration,
    see market/views.py::fleet_register_vendor) as well as admin's company
    detail view. A company owns zero or more of these; nothing requires it
    to own any to keep using its trucks/drivers."""
    owned = []
    for vendor_type in VENDOR_TYPES:
        for v in get_all_vendors(vendor_type):
            if v.get('company_id') == company_id:
                owned.append({**v, 'vendor_type': vendor_type})
    return owned


def get_vendor_categories(vendor):
    """Mirrors market/markets.py::get_market_categories for the same
    comma-separated categories field, shared shape across all three vendor
    types."""
    raw = (vendor or {}).get('categories') or ''
    return [c.strip() for c in raw.split(',') if c.strip()]


def get_vendor(vendor_type, vendor_id):
    """Resolves any of the three vendor types to its row. Market dispatches
    to the existing markets.py so callers don't need to special-case it."""
    if not vendor_type or not vendor_id:
        return None
    if vendor_type == 'market':
        from .markets import get_market
        return get_market(vendor_id)
    return next((v for v in get_all_vendors(vendor_type) if v['id'] == vendor_id), None)


def get_effective_min_order_kg(vendor, zone):
    """The real minimum basket weight (kg) a B2C order from this
    warehouse/industry must meet, for the given zone ('close' or 'far') —
    see market/views.py::_vendor_order_eligibility for how zone is
    determined. Fallback cascade: close zone tries the vendor's own
    close-radius override first, then falls through to the same standard-
    minimum cascade the far zone always uses — vendor's own min_order_kg,
    then the platform-wide default. Never None."""
    from .site_settings import get_default_min_order_kg

    standard = vendor.get('min_order_kg')
    if standard is None:
        standard = get_default_min_order_kg()
    if zone == 'close':
        close_override = vendor.get('close_radius_min_order_kg')
        if close_override is not None:
            return float(close_override)
    return float(standard)


def create_vendor(vendor_type, region, name, notes, lat, lng, contact_phone='', contact_person='',
                   landmark_note='', categories=None, logo_url=None, company_id=None,
                   storage_capacity_note='', license_number='', bulk_only=True,
                   min_order_kg=None, close_radius_min_order_kg=None, region_id=None,
                   free_delivery_min_kg=None, free_picking_min_kg=None):
    table = _TABLE_BY_TYPE[vendor_type]
    row = {
        'region': (region or '').strip(),
        'region_id': region_id,
        'name': name.strip(),
        'notes': (notes or '').strip(),
        'lat': lat,
        'lng': lng,
        'contact_phone': (contact_phone or '').strip(),
        'contact_person': (contact_person or '').strip(),
        'landmark_note': (landmark_note or '').strip(),
        'categories': ','.join(categories or []),
        'logo_url': logo_url,
        'company_id': company_id,
        'min_order_kg': min_order_kg,
        'close_radius_min_order_kg': close_radius_min_order_kg,
        'free_delivery_min_kg': free_delivery_min_kg,
        'free_picking_min_kg': free_picking_min_kg,
    }
    if vendor_type == 'warehouse':
        row['storage_capacity_note'] = (storage_capacity_note or '').strip()
    elif vendor_type == 'industry':
        row['license_number'] = (license_number or '').strip()
        row['bulk_only'] = bulk_only
    get_client().table(table).insert(row).execute()
    _invalidate(vendor_type)


def update_vendor(vendor_type, vendor_id, region, name, notes, lat, lng, contact_phone='', contact_person='',
                   landmark_note='', categories=None, logo_url=None, company_id=None,
                   storage_capacity_note=None, license_number=None, bulk_only=None,
                   min_order_kg=None, close_radius_min_order_kg=None, region_id=None,
                   free_delivery_min_kg=None, free_picking_min_kg=None):
    table = _TABLE_BY_TYPE[vendor_type]
    row = {
        'region': (region or '').strip(),
        'region_id': region_id,
        'name': name.strip(),
        'notes': (notes or '').strip(),
        'lat': lat,
        'lng': lng,
        'contact_phone': (contact_phone or '').strip(),
        'contact_person': (contact_person or '').strip(),
        'landmark_note': (landmark_note or '').strip(),
        'company_id': company_id,
        'min_order_kg': min_order_kg,
        'close_radius_min_order_kg': close_radius_min_order_kg,
        'free_delivery_min_kg': free_delivery_min_kg,
        'free_picking_min_kg': free_picking_min_kg,
    }
    if categories is not None:
        row['categories'] = ','.join(categories)
    if logo_url is not None:
        row['logo_url'] = logo_url
    if vendor_type == 'warehouse' and storage_capacity_note is not None:
        row['storage_capacity_note'] = storage_capacity_note.strip()
    elif vendor_type == 'industry':
        if license_number is not None:
            row['license_number'] = license_number.strip()
        if bulk_only is not None:
            row['bulk_only'] = bulk_only
    get_client().table(table).update(row).eq('id', vendor_id).execute()
    _invalidate(vendor_type)


def toggle_vendor_active(vendor_type, vendor_id):
    table = _TABLE_BY_TYPE[vendor_type]
    client = get_client()
    resp = client.table(table).select('is_active').eq('id', vendor_id).execute()
    if resp.data:
        current = resp.data[0]['is_active']
        client.table(table).update({'is_active': not current}).eq('id', vendor_id).execute()
        _invalidate(vendor_type)
        return not current
    return None


def delete_vendor(vendor_type, vendor_id):
    table = _TABLE_BY_TYPE[vendor_type]
    get_client().table(table).delete().eq('id', vendor_id).execute()
    _invalidate(vendor_type)


# --- Order-tie + payment ledger (warehouse/industry only; market orders
# keep using market/shop_payments.py's own tables unchanged) ---------------

def record_order_vendor_tie(order_id, vendor_type, vendor_id):
    get_client().table('pickker_order_vendors').insert({
        'order_id': order_id, 'vendor_type': vendor_type, 'vendor_id': vendor_id,
    }).execute()


def get_order_vendor_tie(order_id):
    """The single non-market vendor tie for this order, if any, with the
    vendor's own row merged in (name/contact/landmark) — None for every
    ordinary order (the vast majority)."""
    resp = get_client().table('pickker_order_vendors').select('*').eq('order_id', order_id).execute()
    if not resp.data:
        return None
    tie = resp.data[0]
    vendor = get_vendor(tie['vendor_type'], tie['vendor_id']) or {}
    paid = bool(get_vendor_payments_for_order(order_id))
    return {
        **vendor,
        'vendor_type': tie['vendor_type'],
        'vendor_id': tie['vendor_id'],
        'requires_payment': tie['requires_payment'],
        'picked_up_at': tie['picked_up_at'],
        'picked_up': tie['picked_up_at'] is not None,
        'payment_complete': paid or not tie['requires_payment'],
    }


def mark_vendor_picked_up(order_id):
    get_client().table('pickker_order_vendors').update({
        'picked_up_at': datetime.now(timezone.utc).isoformat(),
    }).eq('order_id', order_id).execute()


def get_vendor_payments_for_order(order_id):
    resp = (
        get_client().table('pickker_vendor_payments')
        .select('*')
        .eq('order_id', order_id)
        .order('paid_at', desc=True)
        .execute()
    )
    return resp.data


def record_vendor_payment(order_id, vendor_type, vendor_id, amount, admin_user_id, admin_name, reference_note=''):
    get_client().table('pickker_vendor_payments').insert({
        'order_id': order_id,
        'vendor_type': vendor_type,
        'vendor_id': vendor_id,
        'amount': str(amount),
        'paid_by_admin_id': admin_user_id,
        'paid_by_admin_name': admin_name,
        'reference_note': (reference_note or '').strip(),
    }).execute()


def get_order_vendor_ties_batch(order_ids):
    """{order_id: tie_dict} for a whole order list at once — same shape as
    get_order_vendor_tie, used by the admin orders table so it doesn't run
    one query per order (see accounts/views.py::admin_portal)."""
    order_ids = list({oid for oid in order_ids if oid})
    if not order_ids:
        return {}
    client = get_client()
    ties = client.table('pickker_order_vendors').select('*').in_('order_id', order_ids).execute().data
    if not ties:
        return {}
    payments = client.table('pickker_vendor_payments').select('order_id').in_('order_id', [t['order_id'] for t in ties]).execute().data
    paid_order_ids = {p['order_id'] for p in payments}
    by_order = {}
    for tie in ties:
        vendor = get_vendor(tie['vendor_type'], tie['vendor_id']) or {}
        by_order[tie['order_id']] = {
            **vendor,
            'vendor_type': tie['vendor_type'],
            'vendor_id': tie['vendor_id'],
            'requires_payment': tie['requires_payment'],
            'picked_up_at': tie['picked_up_at'],
            'picked_up': tie['picked_up_at'] is not None,
            'payment_complete': tie['order_id'] in paid_order_ids or not tie['requires_payment'],
        }
    return by_order


def is_vendor_payment_complete(order_id):
    """True for every ordinary order (nothing tied here at all). For an
    order tied to a warehouse/industry, true once at least one payment is
    logged against it — same gate shape as
    market/shop_payments.py::is_shop_payment_complete, kept as a fully
    separate function since it reads a separate table."""
    tie = get_order_vendor_tie(order_id)
    if not tie or not tie['requires_payment']:
        return True
    return bool(get_vendor_payments_for_order(order_id))
