"""
Device Fingerprinting for License Management

Generates stable hardware fingerprints to prevent license key sharing.
Based on the Rivet License Integration Guide.
"""

import hashlib
import uuid
import platform
import socket
import logging
import subprocess
from pathlib import Path
from typing import Dict, Any

logger = logging.getLogger(__name__)


class DeviceFingerprintGenerator:
    """Generate stable device fingerprints for license validation."""

    _FINGERPRINT_VERSION = "v2"

    @staticmethod
    def _legacy_generate() -> str:
        """Legacy fingerprint algorithm kept for backward compatibility."""
        mac_address = ':'.join([
            '{:02x}'.format((uuid.getnode() >> ele) & 0xff)
            for ele in range(0, 8 * 6, 8)
        ][::-1])

        machine_id = str(uuid.getnode())
        architecture = platform.machine()
        hostname = socket.gethostname()

        combined = f"{mac_address}:{machine_id}:{architecture}:{hostname}"
        fingerprint = hashlib.sha512(combined.encode()).hexdigest()
        return f"sha512:{fingerprint}"

    @staticmethod
    def _read_first_existing(paths: list[str]) -> str:
        for file_path in paths:
            try:
                value = Path(file_path).read_text(encoding="utf-8").strip()
                if value:
                    return value
            except Exception:
                continue
        return ""

    @staticmethod
    def _get_windows_machine_guid() -> str:
        try:
            import winreg  # type: ignore

            key = winreg.OpenKey(
                winreg.HKEY_LOCAL_MACHINE,
                r"SOFTWARE\Microsoft\Cryptography",
            )
            value, _ = winreg.QueryValueEx(key, "MachineGuid")
            return str(value).strip()
        except Exception:
            return ""

    @staticmethod
    def _get_macos_platform_uuid() -> str:
        commands = [
            ["ioreg", "-rd1", "-c", "IOPlatformExpertDevice"],
            ["system_profiler", "SPHardwareDataType"],
        ]

        for cmd in commands:
            try:
                output = subprocess.check_output(cmd, stderr=subprocess.DEVNULL, text=True)
            except Exception:
                continue

            for line in output.splitlines():
                stripped = line.strip()
                if "IOPlatformUUID" in stripped and "=" in stripped:
                    return stripped.split("=", 1)[1].strip().strip('"')
                if "Hardware UUID" in stripped and ":" in stripped:
                    return stripped.split(":", 1)[1].strip()

        return ""

    @staticmethod
    def _get_stable_machine_identifier() -> str:
        system = platform.system().lower()

        if system == "darwin":
            machine_id = DeviceFingerprintGenerator._get_macos_platform_uuid()
            if machine_id:
                return machine_id

        if system == "linux":
            machine_id = DeviceFingerprintGenerator._read_first_existing(
                ["/etc/machine-id", "/var/lib/dbus/machine-id"]
            )
            if machine_id:
                return machine_id

        if system == "windows":
            machine_guid = DeviceFingerprintGenerator._get_windows_machine_guid()
            if machine_guid:
                return machine_guid

        node = uuid.getnode()
        # Ignore locally administered/randomized MAC values when possible.
        if (node >> 40) & 0x02 == 0:
            return f"mac:{node:012x}"

        return f"host:{socket.gethostname().lower()}"

    @staticmethod
    def generate() -> str:
        """
        Generate a stable hardware fingerprint for this device.
        
        Requirements:
        - Stability: Fingerprint should remain constant across app restarts
        - Uniqueness: Different devices should generate different fingerprints
        - Privacy: Don't include user-identifiable information
        
        Returns:
            str: Format "sha256:hexdigest"
        """
        try:
            stable_id = DeviceFingerprintGenerator._get_stable_machine_identifier()
            combined = f"rivet:{DeviceFingerprintGenerator._FINGERPRINT_VERSION}:{stable_id}"
            fingerprint = hashlib.sha512(combined.encode("utf-8")).hexdigest()

            logger.debug(f"Generated device fingerprint: sha512:{fingerprint[:16]}...")
            return f"sha512:{fingerprint}"

        except Exception as e:
            logger.error(f"Failed to generate device fingerprint: {e}")
            return DeviceFingerprintGenerator._legacy_generate()

    @staticmethod
    def generate_candidates() -> list[str]:
        """Return acceptable fingerprints for this machine (new + legacy)."""
        candidates: list[str] = []

        try:
            candidates.append(DeviceFingerprintGenerator.generate())
        except Exception:
            pass

        try:
            candidates.append(DeviceFingerprintGenerator._legacy_generate())
        except Exception:
            pass

        # Preserve order while deduplicating.
        deduped: list[str] = []
        seen: set[str] = set()
        for value in candidates:
            if value and value not in seen:
                seen.add(value)
                deduped.append(value)

        return deduped

    @staticmethod
    def generate_metadata() -> Dict[str, Any]:
        """
        Generate device metadata for license validation.
        
        This information is sent to the license server for support
        and analytics purposes. It's not used for device identification.
        
        Returns:
            dict: Device metadata including OS, architecture, versions
        """
        try:
            import sys
            
            metadata = {
                "os": platform.system(),
                "os_version": platform.release(),
                "architecture": platform.machine(),
                "hostname": socket.gethostname(),
                "python_version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
                "platform": platform.platform(),
            }
            
            # Add app version if available
            try:
                from app.config.config import APP_VERSION
                metadata["app_version"] = APP_VERSION
            except (ImportError, AttributeError):
                metadata["app_version"] = "unknown"
            
            return metadata
            
        except Exception as e:
            logger.error(f"Failed to generate device metadata: {e}")
            return {
                "os": "unknown",
                "error": str(e)
            }
