"""Load discovery filters: vehicle types, weight buckets, dimensions, fleet matching."""

from __future__ import annotations

import re
from decimal import Decimal

from django.conf import settings as django_settings
from django.db.models import Q

from accounts.models import TransporterProfile, Vehicle
from core.countries import is_valid_country_code, normalize_country_code

DISTANCE_RANGES_KM = (10, 50, 100)

VEHICLE_TYPES: tuple[str, ...] = (
    'Flat Bed 12m',
    'Container 40 Feet / 20 Feet',
    'Flat Bed 15m',
    'Pickup Box 3 Ton',
    'Pickup Box 7 Ton',
    'Pickup Box 10 Ton',
    'Box Trailer 15m',
    'Box Trailer 13.5m',
    'Box Trailer 12m',
    '3 Ton Pickup',
    '7 Ton Pickup',
    '10 Ton Pickup (With Lifter)',
    'Side Curtain 7 Ton',
    'Side Curtain 10 Ton',
    'Side Curtain 13.5',
    'Side Curtain 15m',
    'Recovery 3 Ton',
    'Recovery 7 Ton',
    'Recovery 10 Ton',
    'Low Bed 2 Axle',
    'Low Bed 3 Axle',
    'Car Carrier',
    'Refrigerator Trailer 12m',
    'Refrigerator Trailer 13.5m',
    'Refrigerator Trailer 15m',
    'Container 40 Feet & 20 Feet Both',
)

WEIGHT_BUCKETS: dict[str, dict[str, str | float | None]] = {
    'up_to_2t': {'label': 'Up to 2T', 'min_tons': None, 'max_tons': 2.0},
    '2_5t': {'label': '2-5T', 'min_tons': 2.0, 'max_tons': 5.0},
    '5_8t': {'label': '5-8T', 'min_tons': 5.0, 'max_tons': 8.0},
    '8_10t': {'label': '8-10T', 'min_tons': 8.0, 'max_tons': 10.0},
    '10t_plus': {'label': '10T+', 'min_tons': 10.0, 'max_tons': None},
}

_VEHICLE_TYPE_LOOKUP = {name.casefold(): name for name in VEHICLE_TYPES}


def normalize_vehicle_type(value: str | None) -> str | None:
    if not value:
        return None
    key = str(value).strip().casefold()
    return _VEHICLE_TYPE_LOOKUP.get(key)


def is_valid_vehicle_type(value: str | None) -> bool:
    return normalize_vehicle_type(value) is not None


def is_valid_weight_bucket(value: str | None) -> bool:
    if not value:
        return False
    return str(value).strip().lower() in WEIGHT_BUCKETS


def is_valid_distance_range(value: str | None) -> bool:
    if not value:
        return False
    return str(value).strip() in {str(k) for k in DISTANCE_RANGES_KM}


def filter_options_payload() -> dict:
    return {
        'available_distance_ranges_km': list(DISTANCE_RANGES_KM),
        'available_weight_buckets': [
            {'id': key, 'label': meta['label']}
            for key, meta in WEIGHT_BUCKETS.items()
        ],
        'available_vehicle_types': list(VEHICLE_TYPES),
    }


def extract_weight_tons(weight_text) -> float | None:
    if weight_text is None or weight_text == '':
        return None
    weight_str = str(weight_text).strip()
    match = re.search(r'(\d+(?:\.\d+)?)', weight_str)
    if not match:
        return None
    try:
        value = float(match.group(1))
    except (TypeError, ValueError):
        return None
    unit_text = weight_str.lower()
    has_letters = bool(re.search(r'[a-zA-Z]', unit_text))
    default_unit = str(getattr(django_settings, 'WEIGHT_NUMERIC_DEFAULT_UNIT', 'kg')).strip().lower()
    if 'kg' in unit_text:
        return value / 1000.0
    if 'ton' in unit_text:
        return value
    if 'lb' in unit_text:
        return (value * 0.453592) / 1000.0
    if not has_letters:
        if default_unit == 'kg':
            return value / 1000.0
        return value
    if default_unit == 'kg':
        return value / 1000.0
    return value


def weight_in_bucket(tons: float | None, bucket_key: str) -> bool:
    if tons is None:
        return False
    meta = WEIGHT_BUCKETS[bucket_key]
    min_tons = meta['min_tons']
    max_tons = meta['max_tons']
    if min_tons is None and max_tons is not None:
        return tons <= float(max_tons)
    if max_tons is None and min_tons is not None:
        return tons > float(min_tons)
    if min_tons is not None and max_tons is not None:
        return float(min_tons) < tons <= float(max_tons)
    return False


def _dimension_triple_meters(text) -> tuple[float, float, float] | None:
    if not text:
        return None
    raw = str(text).strip().lower()
    nums = re.findall(r'(\d+(?:\.\d+)?)', raw)
    if len(nums) < 3:
        return None
    try:
        vals = [float(x) for x in nums[:3]]
    except (TypeError, ValueError):
        return None
    if 'ft' in raw or 'feet' in raw:
        vals = [v * 0.3048 for v in vals]
    vals.sort()
    return vals[0], vals[1], vals[2]


def dimensions_fit(required_text, capacity_text) -> bool:
    """True when required (shipment) sorted dims fit within capacity (filter/vehicle)."""
    required = _dimension_triple_meters(required_text)
    capacity = _dimension_triple_meters(capacity_text)
    if required is None or capacity is None:
        return required is None
    return all(req <= cap + 1e-9 for req, cap in zip(required, capacity))


def _vehicle_max_dims_m(vehicle) -> tuple[float, float, float] | None:
    if vehicle.max_length_m is None or vehicle.max_width_m is None or vehicle.max_height_m is None:
        return None
    try:
        vals = sorted([
            float(vehicle.max_length_m),
            float(vehicle.max_width_m),
            float(vehicle.max_height_m),
        ])
        return vals[0], vals[1], vals[2]
    except (TypeError, ValueError):
        return None


def _types_compatible(vehicle_type: str, shipment_type_required: str) -> bool:
    v_norm = normalize_vehicle_type(vehicle_type)
    s_norm = normalize_vehicle_type(shipment_type_required)
    if v_norm and s_norm:
        return v_norm == s_norm
    return str(vehicle_type).strip().casefold() == str(shipment_type_required).strip().casefold()


def vehicle_type_list(vehicle) -> list[str]:
    """Return all types for a vehicle (JSON list, else legacy vehicle_type)."""
    if hasattr(vehicle, 'type_list'):
        return vehicle.type_list()
    types = getattr(vehicle, 'vehicle_types', None) or []
    if isinstance(types, list):
        cleaned = [str(t).strip() for t in types if str(t).strip()]
        if cleaned:
            return cleaned
    primary = (getattr(vehicle, 'vehicle_type', None) or '').strip()
    return [primary] if primary else []


def vehicle_matches_required_type(vehicle, shipment_type_required: str) -> bool:
    """True if any of the vehicle's types is compatible with the shipment requirement."""
    return any(
        _types_compatible(vt, shipment_type_required)
        for vt in vehicle_type_list(vehicle)
    )


def matching_vehicle_type(vehicle, shipment_type_required: str) -> str | None:
    """Return the first vehicle type that matches the shipment requirement."""
    for vt in vehicle_type_list(vehicle):
        if _types_compatible(vt, shipment_type_required):
            return normalize_vehicle_type(vt) or vt
    return None


def _vehicle_fits_shipment(vehicle, shipment) -> bool:
    if not vehicle_matches_required_type(vehicle, shipment.vehicle_type_required):
        return False
    shipment_tons = extract_weight_tons(shipment.weight)
    if shipment_tons is not None:
        try:
            capacity_tons = float(vehicle.load_capacity) / 1000.0
        except (TypeError, ValueError):
            capacity_tons = None
        if capacity_tons is not None and shipment_tons > capacity_tons + 1e-9:
            return False
    v_dims = _vehicle_max_dims_m(vehicle)
    s_dims = _dimension_triple_meters(shipment.dimensions)
    if v_dims and s_dims:
        if any(sd > vd + 1e-9 for sd, vd in zip(s_dims, v_dims)):
            return False
    return True


def find_matching_vehicle(shipment, vehicles):
    for vehicle in vehicles:
        if _vehicle_fits_shipment(vehicle, shipment):
            return vehicle
    return None


def shipment_passes_discovery_filters(
    shipment,
    *,
    vehicle_type: str | None,
    weight_bucket: str | None,
    dimensions: str | None,
) -> bool:
    if vehicle_type:
        required = normalize_vehicle_type(shipment.vehicle_type_required)
        if required != vehicle_type:
            return False
    if weight_bucket:
        if not weight_in_bucket(extract_weight_tons(shipment.weight), weight_bucket):
            return False
    if dimensions:
        if not dimensions_fit(shipment.dimensions, dimensions):
            return False
    return True


def vehicle_capacity_tons(vehicle) -> float | None:
    try:
        return float(vehicle.load_capacity) / 1000.0
    except (TypeError, ValueError):
        return None


def parse_required_country_code(value: str | None) -> str | None:
    """Return normalized ISO country code or None when missing/invalid."""
    if not value or not str(value).strip():
        return None
    code = normalize_country_code(value)
    if not code or not is_valid_country_code(code):
        return None
    return code


def transporter_verified_active_vehicles(user, account_type):
    qs = Vehicle.objects.filter(is_verified=True, is_active=True)
    if account_type == TransporterProfile.AccountType.FLEET_OWNER:
        return list(qs.filter(owner=user))
    return list(qs.filter(Q(owner=user) | Q(assigned_driver=user)))


def shipment_matches_transporter_vehicle_types(shipment, vehicles) -> bool:
    return any(
        vehicle_matches_required_type(v, shipment.vehicle_type_required)
        for v in vehicles
    )
