from __future__ import annotations

from datetime import datetime, timedelta, timezone
from typing import Optional

from SRC.Services.license_crypto import LicenseCryptoError, payload_digest, verify_signature
from SRC.Services.license_machine import build_machine_fingerprint, build_machine_fingerprint_aliases
from SRC.Services.license_models import LicenseEnvelope, LicenseRuntimeState, LicenseValidationResult
from SRC.Services.license_state import (
    build_default_state,
    detect_clock_rollback,
    ensure_state_machine_binding,
    is_state_integrity_valid,
    parse_utc,
    update_state_after_validation,
    utc_now,
)


UTC = timezone.utc
EXPECTED_PRODUCT = "DPS TRAIL"
EXPECTED_EDITION = "MONOPOSTE"


class LicenseValidationError(RuntimeError):
    pass


def _parse_date(value: str, *, end_of_day: bool = False) -> datetime:
    base = datetime.fromisoformat(value.replace("Z", "+00:00"))
    if base.tzinfo is None:
        if end_of_day:
            return base.replace(hour=23, minute=59, second=59, microsecond=0, tzinfo=UTC)
        return base.replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=UTC)
    return base.astimezone(UTC)


def _canonical_token(value: str) -> str:
    return "".join(ch for ch in (value or "").upper() if ch.isalnum())


def _matches_expected_token(actual: str, expected: str) -> bool:
    return _canonical_token(actual) == _canonical_token(expected)


def validate_license(
    envelope: Optional[LicenseEnvelope],
    state: Optional[LicenseRuntimeState],
) -> tuple[LicenseValidationResult, Optional[LicenseRuntimeState]]:
    machine = build_machine_fingerprint()
    accepted_machine_fingerprints = build_machine_fingerprint_aliases(machine.components)
    now = utc_now()

    if envelope is None:
        return (
            LicenseValidationResult(False, "MISSING_LICENSE", "Aucune licence locale n'a été trouvée.", now_utc=now),
            state,
        )

    payload = envelope.payload
    if state is None:
        state = build_default_state(machine.fingerprint)

    if not is_state_integrity_valid(state):
        return (
            LicenseValidationResult(False, "STATE_TAMPERED", "Les métadonnées locales de licence ont été altérées.", envelope=envelope, payload=payload, now_utc=now),
            state,
        )

    if not ensure_state_machine_binding(state, machine.fingerprint):
        return (
            LicenseValidationResult(False, "STATE_MACHINE_MISMATCH", "L'état local ne correspond pas à cette machine.", envelope=envelope, payload=payload, now_utc=now),
            state,
        )

    if detect_clock_rollback(state, now):
        return (
            LicenseValidationResult(False, "CLOCK_ROLLBACK", "L'horloge système semble avoir été reculée. Une réactivation sera nécessaire.", envelope=envelope, payload=payload, now_utc=now),
            state,
        )

    if not _matches_expected_token(payload.product, EXPECTED_PRODUCT):
        actual = payload.product or "<vide>"
        return (
            LicenseValidationResult(False, "WRONG_PRODUCT", f"La licence fournie ne correspond pas à DPS TRAIL. Produit reçu : {actual}", envelope=envelope, payload=payload, now_utc=now),
            state,
        )
    if not _matches_expected_token(payload.edition, EXPECTED_EDITION):
        actual = payload.edition or "<vide>"
        return (
            LicenseValidationResult(False, "WRONG_EDITION", f"La licence fournie ne correspond pas à l'édition monoposte. Édition reçue : {actual}", envelope=envelope, payload=payload, now_utc=now),
            state,
        )
    if payload.status.upper() not in {"ACTIVE", "READY"}:
        return (
            LicenseValidationResult(False, "INACTIVE_LICENSE", "La licence est désactivée ou révoquée.", envelope=envelope, payload=payload, now_utc=now),
            state,
        )

    try:
        verify_signature(envelope.signature_payload(), envelope.signature, envelope.public_key_base64)
    except LicenseCryptoError as exc:
        return (
            LicenseValidationResult(False, "INVALID_SIGNATURE", str(exc), envelope=envelope, payload=payload, now_utc=now),
            state,
        )

    if payload.machine_fingerprint not in accepted_machine_fingerprints:
        return (
            LicenseValidationResult(False, "MACHINE_MISMATCH", "La licence locale a été émise pour une autre machine.", envelope=envelope, payload=payload, now_utc=now),
            state,
        )

    if payload.current_installation_id and state.installation_id and payload.current_installation_id != state.installation_id:
        return (
            LicenseValidationResult(False, "INSTALLATION_MISMATCH", "La licence locale est liée à une autre installation logicielle.", envelope=envelope, payload=payload, now_utc=now),
            state,
        )

    start_date = _parse_date(payload.start_date)
    end_date = _parse_date(payload.end_date, end_of_day=True)
    if now < start_date:
        return (
            LicenseValidationResult(False, "NOT_YET_ACTIVE", "La licence n'est pas encore active.", envelope=envelope, payload=payload, now_utc=now),
            state,
        )
    if now > end_date:
        return (
            LicenseValidationResult(False, "EXPIRED", "La licence est expirée.", envelope=envelope, payload=payload, now_utc=now, days_until_expiry=0),
            state,
        )

    reference_check = parse_utc(state.last_successful_check_utc) or now
    offline_deadline = reference_check + timedelta(days=payload.offline_grace_days)
    if now > offline_deadline:
        return (
            LicenseValidationResult(
                False,
                "OFFLINE_GRACE_EXCEEDED",
                f"Aucune connexion validée depuis plus de {payload.offline_grace_days} jours. Reconnexion requise.",
                envelope=envelope,
                payload=payload,
                now_utc=now,
                days_until_expiry=max((end_date.date() - now.date()).days, 0),
            ),
            state,
        )

    updated_state = update_state_after_validation(
        state,
        now_utc=now,
        license_hash=payload_digest(envelope.signature_payload()),
        mark_successful_check=False,
    )
    return (
        LicenseValidationResult(
            True,
            "VALID",
            "Licence locale valide.",
            envelope=envelope,
            payload=payload,
            now_utc=now,
            days_until_expiry=max((end_date.date() - now.date()).days, 0),
        ),
        updated_state,
    )
