import json
import os
import random
import string
from base64 import b64encode
from urllib.error import HTTPError, URLError
from urllib.parse import urljoin
from urllib.request import Request, urlopen

from accounts.models import TransporterProfile


def generate_unique_tc_uid(length=10):
    alphabet = string.ascii_uppercase + string.digits
    for _ in range(20):
        candidate = ''.join(random.choices(alphabet, k=length))
        if not TransporterProfile.objects.filter(tc_u_id=candidate).exists():
            return candidate
    raise ValueError('Unable to generate unique Traccar unique ID.')


def create_traccar_device(unique_id, device_name=None):
    base_url = os.getenv('TRACCAR_URL', '').strip()
    username = os.getenv('TRACCAR_USERNAME', '').strip()
    password = os.getenv('TRACCAR_PASSWORD', '').strip()

    if not base_url or not username or not password:
        raise ValueError('Traccar credentials are not configured.')

    endpoint = urljoin(base_url if base_url.endswith('/') else f'{base_url}/', 'api/devices')
    resolved_name = (device_name or '').strip() or unique_id
    payload = json.dumps({'name': resolved_name, 'uniqueId': unique_id}).encode('utf-8')
    auth = b64encode(f'{username}:{password}'.encode('utf-8')).decode('ascii')

    req = Request(endpoint, data=payload, method='POST')
    req.add_header('Content-Type', 'application/json')
    req.add_header('Accept', 'application/json')
    req.add_header('Authorization', f'Basic {auth}')

    try:
        with urlopen(req, timeout=15) as res:
            body = res.read().decode('utf-8') or '{}'
            data = json.loads(body)
    except HTTPError as exc:
        detail = exc.read().decode('utf-8', errors='ignore') if hasattr(exc, 'read') else str(exc)
        raise ValueError(f'Traccar device creation failed: {detail or exc.reason}')
    except (URLError, TimeoutError) as exc:
        raise ValueError(f'Traccar connection failed: {exc}')
    except json.JSONDecodeError:
        raise ValueError('Traccar returned invalid JSON.')

    if not isinstance(data, dict) or 'id' not in data:
        raise ValueError('Traccar device creation response missing id.')
    return data


def get_latest_device_position(device_id):
    base_url = os.getenv('TRACCAR_URL', '').strip()
    username = os.getenv('TRACCAR_USERNAME', '').strip()
    password = os.getenv('TRACCAR_PASSWORD', '').strip()

    if not base_url or not username or not password:
        raise ValueError('Traccar credentials are not configured.')

    endpoint = urljoin(base_url if base_url.endswith('/') else f'{base_url}/', f'api/positions?deviceId={device_id}')
    auth = b64encode(f'{username}:{password}'.encode('utf-8')).decode('ascii')

    req = Request(endpoint, method='GET')
    req.add_header('Accept', 'application/json')
    req.add_header('Authorization', f'Basic {auth}')

    try:
        with urlopen(req, timeout=15) as res:
            body = res.read().decode('utf-8') or '[]'
            data = json.loads(body)
    except HTTPError as exc:
        detail = exc.read().decode('utf-8', errors='ignore') if hasattr(exc, 'read') else str(exc)
        raise ValueError(f'Traccar positions fetch failed: {detail or exc.reason}')
    except (URLError, TimeoutError) as exc:
        raise ValueError(f'Traccar connection failed: {exc}')
    except json.JSONDecodeError:
        raise ValueError('Traccar returned invalid JSON for positions.')

    if not isinstance(data, list) or not data:
        raise ValueError('No position found for this device.')

    latest = data[0]
    return {
        'latitude': latest.get('latitude'),
        'longitude': latest.get('longitude'),
        'raw': latest,
    }


def get_latest_positions_map():
    """
    Return latest position per device id from Traccar /api/positions.
    Output: {device_id(str): {'latitude': x, 'longitude': y, 'raw': {...}}}
    """
    base_url = os.getenv('TRACCAR_URL', '').strip()
    username = os.getenv('TRACCAR_USERNAME', '').strip()
    password = os.getenv('TRACCAR_PASSWORD', '').strip()

    if not base_url or not username or not password:
        raise ValueError('Traccar credentials are not configured.')

    endpoint = urljoin(base_url if base_url.endswith('/') else f'{base_url}/', 'api/positions')
    auth = b64encode(f'{username}:{password}'.encode('utf-8')).decode('ascii')

    req = Request(endpoint, method='GET')
    req.add_header('Accept', 'application/json')
    req.add_header('Authorization', f'Basic {auth}')

    try:
        with urlopen(req, timeout=20) as res:
            body = res.read().decode('utf-8') or '[]'
            data = json.loads(body)
    except HTTPError as exc:
        detail = exc.read().decode('utf-8', errors='ignore') if hasattr(exc, 'read') else str(exc)
        raise ValueError(f'Traccar positions fetch failed: {detail or exc.reason}')
    except (URLError, TimeoutError) as exc:
        raise ValueError(f'Traccar connection failed: {exc}')
    except json.JSONDecodeError:
        raise ValueError('Traccar returned invalid JSON for positions.')

    if not isinstance(data, list):
        raise ValueError('Traccar positions response is invalid.')

    latest = {}
    for item in data:
        did = item.get('deviceId')
        lat = item.get('latitude')
        lon = item.get('longitude')
        if did is None or lat is None or lon is None:
            continue
        key = str(did)
        prev = latest.get(key)
        # Use greatest position id as newest fallback.
        if not prev or int(item.get('id') or 0) >= int(prev['raw'].get('id') or 0):
            latest[key] = {'latitude': lat, 'longitude': lon, 'raw': item}
    return latest
