from __future__ import annotations

from copy import deepcopy
from datetime import date, datetime
from typing import Any

from mx_bulk_commerce import is_lifetime_expiry


class BulkLicenseError(ValueError):
    """Raised when a bulk-license redemption cannot be accepted."""


def _positive_int(value: Any, default: int = 1) -> int:
    try:
        return max(1, int(value))
    except Exception:
        return default


def _nonnegative_int(value: Any, default: int = 0) -> int:
    try:
        return max(0, int(value))
    except Exception:
        return default


def _as_dict(value: Any) -> dict[str, Any]:
    return dict(value) if isinstance(value, dict) else {}


def _date_from_iso(value: str) -> date:
    raw = str(value or '').strip()
    if raw.endswith('Z'):
        raw = raw[:-1] + '+00:00'
    try:
        return datetime.fromisoformat(raw).date()
    except Exception:
        return datetime.utcnow().date()


def _validate_expiry(expiry: Any, *, now_iso: str) -> None:
    raw = str(expiry or '').strip()
    if not raw or is_lifetime_expiry(raw):
        return
    try:
        expiry_date = datetime.strptime(raw[:10], '%Y-%m-%d').date()
    except Exception:
        # Keep compatibility with the existing status endpoint, which ignores
        # unparseable legacy expiry values instead of invalidating the key.
        return
    if expiry_date < _date_from_iso(now_iso):
        raise BulkLicenseError('This bulk key has expired.')


def apply_bulk_redemption(
    current: Any,
    *,
    uid: str,
    email: str,
    device_id: str,
    device_name: str = '',
    now_iso: str,
) -> tuple[dict[str, Any], dict[str, Any]]:
    """Apply one idempotent bulk-key redemption to a Firebase record.

    A UID consumes at most one slot.  Once bound, the slot stays on the same
    device until an administrator clears that redemption's device_id.  A
    cleared binding can be attached to a replacement PC without consuming a
    second slot.
    """
    if not isinstance(current, dict) or not current:
        raise BulkLicenseError('Bulk key not found.')

    clean_uid = str(uid or '').strip()
    clean_email = str(email or '').strip().lower()
    clean_device = str(device_id or '').strip()
    clean_name = str(device_name or '').strip()
    if not clean_uid or not clean_email or not clean_device:
        raise BulkLicenseError('Account and device information are required.')

    record = deepcopy(current)
    if record.get('active', True) is False:
        raise BulkLicenseError('This bulk key is disabled.')
    _validate_expiry(record.get('expiry', ''), now_iso=now_iso)

    limit = _positive_int(record.get('limit'), 1)
    redemptions = _as_dict(record.get('redemptions'))
    legacy = _as_dict(record.get('redeemed_users'))
    stored_used = _nonnegative_int(record.get('used'), 0)
    effective_used = max(stored_used, len(redemptions) + len(legacy))

    existing = redemptions.get(clean_uid)
    already_redeemed = isinstance(existing, dict)
    if already_redeemed:
        item = dict(existing)
        bound_device = str(item.get('device_id') or '').strip()
        if bound_device and bound_device != clean_device:
            raise BulkLicenseError('This account is already bound to another PC. Reset Device before moving it.')
        item['email'] = clean_email
        item['device_id'] = clean_device
        item['device_name'] = clean_name
        item.setdefault('redeemed_at', now_iso)
        item['last_seen_at'] = now_iso
        redemptions[clean_uid] = item
        used = effective_used
    else:
        if effective_used >= limit:
            raise BulkLicenseError('Bulk key license limit has been reached.')
        redemptions[clean_uid] = {
            'email': clean_email,
            'device_id': clean_device,
            'device_name': clean_name,
            'redeemed_at': now_iso,
            'last_seen_at': now_iso,
        }
        used = effective_used + 1

    record['redemptions'] = redemptions
    record['redeemed_users'] = legacy
    record['limit'] = limit
    record['used'] = used
    record['updated_at'] = now_iso

    result = {
        'used': used,
        'limit': limit,
        'remaining': max(0, limit - used),
        'already_redeemed': already_redeemed,
        'device_id': clean_device,
        'device_name': clean_name,
    }
    return record, result
