from decimal import Decimal, InvalidOperation
import re

from django.utils import timezone


def phone_has_valid_digits(phone):
    digits = re.sub(r'\D+', '', (phone or '').strip())
    return 7 <= len(digits) <= 15


def first_address_line(address):
    trimmed = (address or '').strip()
    if not trimmed:
        return ''
    return trimmed.splitlines()[0].strip()


def derive_location_label(location_label='', address=''):
    label = (location_label or '').strip()
    if label:
        return label[:255]
    return first_address_line(address)[:255]


def parse_decimal_value(raw_value, *, field_label, minimum=None, maximum=None):
    value = (raw_value or '').strip()
    if not value:
        return None

    try:
        parsed = Decimal(value)
    except (InvalidOperation, TypeError):
        raise ValueError(f'Invalid {field_label}.')

    if minimum is not None and parsed < minimum:
        raise ValueError(f'{field_label.capitalize()} must be at least {minimum}.')
    if maximum is not None and parsed > maximum:
        raise ValueError(f'{field_label.capitalize()} must be no more than {maximum}.')
    return parsed


def stringify_decimal(value):
    if value in {None, ''}:
        return ''
    return str(value)


def build_delivery_payload(
    *,
    full_name='',
    phone='',
    address='',
    location_label='',
    delivery_notes='',
    latitude=None,
    longitude=None,
    location_accuracy_m=None,
    save_as_default=True,
    **extra,
):
    payload = {
        'full_name': (full_name or '').strip(),
        'phone': (phone or '').strip(),
        'address': (address or '').strip(),
        'location_label': derive_location_label(location_label, address),
        'delivery_notes': (delivery_notes or '').strip(),
        'latitude': stringify_decimal(latitude),
        'longitude': stringify_decimal(longitude),
        'location_accuracy_m': stringify_decimal(location_accuracy_m),
        'save_as_default': bool(save_as_default),
    }
    payload.update(extra)
    return payload


def profile_delivery_changes(*, phone, address, location_label='', latitude=None, longitude=None, location_accuracy_m=None):
    return {
        'phone': (phone or '').strip(),
        'default_address': (address or '').strip(),
        'location_label': derive_location_label(location_label, address),
        'latitude': latitude,
        'longitude': longitude,
        'location_accuracy_m': location_accuracy_m,
        'location_updated_at': timezone.now() if latitude is not None and longitude is not None else None,
    }


def apply_profile_delivery_fields(profile, **kwargs):
    changes = profile_delivery_changes(**kwargs)
    for field, value in changes.items():
        setattr(profile, field, value)
    return profile


def save_profile_delivery_defaults(profile, **kwargs):
    if profile is None:
        return []

    changes = profile_delivery_changes(**kwargs)
    update_fields = []

    for field, value in changes.items():
        if getattr(profile, field) != value:
            setattr(profile, field, value)
            update_fields.append(field)

    if update_fields:
        profile.save(update_fields=update_fields)

    return update_fields


def delivery_payload_from_profile(profile, *, full_name='', **extra):
    if profile is None:
        return build_delivery_payload(full_name=full_name, **extra)

    return build_delivery_payload(
        full_name=full_name,
        phone=profile.phone,
        address=profile.formatted_delivery_address,
        location_label=profile.short_location,
        latitude=profile.latitude,
        longitude=profile.longitude,
        location_accuracy_m=profile.location_accuracy_m,
        save_as_default=True,
        **extra,
    )
