import os
import subprocess
import math
import platform

class Specs:
    
    @staticmethod
    def check_hard_drive():
        if os.name == 'nt':  # Windows
            try:
                # Use PowerShell to get disk information
                cmd = 'Get-PSDrive C | Select-Object Used,Free'
                output = subprocess.check_output(['powershell', '-Command', cmd], shell=True).decode()
                lines = output.split('\n')
                for line in lines[3:]:  # Skip header lines
                    if line.strip():
                        used_free = line.split()
                        used_bytes = int(used_free[0])
                        free_bytes = int(used_free[1])
                        total_bytes = used_bytes + free_bytes
                        break
                        
            except Exception as e:
                # Fallback method using dir command
                try:
                    cmd = 'dir C:\\'
                    output = subprocess.check_output(cmd, shell=True).decode()
                    lines = output.split('\n')
                    for line in lines:
                        if "bytes free" in line.lower():
                            free_text = line.strip()
                            free_bytes = int(''.join(c for c in free_text.split()[0] if c.isdigit()))
                            total_bytes = free_bytes * 2  # Rough estimation
                            used_bytes = total_bytes - free_bytes
                            break
                except:
                    # Default values if all fails
                    total_bytes = 500 * (2**30)  # 500 GB default
                    free_bytes = 250 * (2**30)
                    used_bytes = 250 * (2**30)

        # Convert to GB
        total = math.ceil(total_bytes / (2 ** 30))
        used = math.ceil(used_bytes / (2 ** 30))
        free = math.ceil(free_bytes / (2 ** 30))
        percentage = (used_bytes / total_bytes) * 100 if total_bytes > 0 else 50

        description = f"HARD DRIVE has a total storage of {total} GigaBytes (Used: {used} GB, Free: {free} GB)"
        explanation = None

        if total < 20:
            score = 0
            explanation = "Very small storage, but possible for basic systems."
        elif total < 50:
            score = 3
            explanation = "Small but acceptable for basic usage."
        elif total < 8060:
            score = 4
            explanation = "Common size for basic systems."
        elif total < 120:
            score = 4
            explanation = "Common size for basic SSDs."
        else:
            score = 5

        return score, description, explanation

    @staticmethod 
    def check_ram_space():
        total = 0  # Initialize total
        
        if os.name == 'nt':  # Windows
            try:
                cmd = 'Get-CimInstance Win32_ComputerSystem | Select-Object TotalPhysicalMemory'
                output = subprocess.check_output(['powershell', '-Command', cmd], shell=True).decode()
                total = int(output.split('\n')[3]) / (2 ** 30)  # Convert to GB
            except:
                pass
        else:  # Unix/Linux/Mac
            try:
                output = subprocess.check_output(['sysctl', '-n', 'hw.memsize']).decode()
                total = int(output) / (2 ** 30)  # Convert to GB
            except:
                try:
                    with open('/proc/meminfo') as f:
                        for line in f:
                            if 'MemTotal' in line:
                                total = int(line.split()[1]) / (2 ** 20)  # Convert KB to GB
                except:
                    pass

        total = math.ceil(total)
        description = f"RAM has a total storage of {total} GigaBytes."
        explanation = None

        if total < 2:
            score = 0
            explanation = "Low RAM but possible for basic systems."
        elif total < 4:
            score = 3
            explanation = "Sufficient RAM for basic usage."
        elif total < 8:
            score = 4
            explanation = "Common RAM size for modern systems."
        else:
            score = 5

        return score, description, explanation

    @staticmethod
    def check_cpu_cores():
        logical_cores = 1

        if os.name == 'nt':  # Windows
            try:
                cmd = 'Get-CimInstance Win32_Processor | Select-Object NumberOfLogicalProcessors'
                output = subprocess.check_output(['powershell', '-Command', cmd], shell=True).decode()
                logical_cores = int(output.split('\n')[3])
            except:
                pass
        else:  # Unix/Linux/Mac
            try:
                logical_cores = len(os.sched_getaffinity(0))
            except:
                try:
                    logical_cores = os.cpu_count()
                except:
                    pass

        description = f"CPU has a total of {logical_cores} logical cores."
        explanation = None

        if logical_cores < 2:
            score = 0
            explanation = "Single core but possible for basic systems."
        elif logical_cores < 4:
            score = 3
            explanation = "Dual core, common for basic systems."
        elif logical_cores < 6:
            score = 4
            explanation = "Quad core, typical for modern systems."
        else:
            score = 5

        return score, description, explanation

    @staticmethod
    def check_serial_number():
        if os.name == 'nt':
            serial = None
            explanation = None
            
            try:
                cmd = 'Get-CimInstance Win32_BIOS | Select-Object SerialNumber'
                output = subprocess.check_output(['powershell', '-Command', cmd], shell=True).decode()
                serial = output.split('\n')[3].strip()

                if str(serial) == "0":
                    score = 1
                    explanation = "Serial number CANNOT be 0 for a real computer."
                else:
                    score = 5

            except Exception as e:
                score = 5
                explanation = f"Something went wrong, so giving benefit of the doubt. Considering this test successful.\nexception: {e}"

            description = f"SERIAL NUMBER is {serial}."

            return score, description, explanation

        else:  # Unix/Linux/Mac
            return 5, "SERIAL NUMBER is None.", "This test can only be run on Windows. Considering this test successful."

    # TODO add all known models of virtual machines
    _MODELS = [
        'virtualbox',
        'vmware',
        'kvm',
        'virtual machine',
        'qemu',
        'xen',
        'hyperv',
        'hyper-v',
        'parallels',
        'virtual platform',
        'vm platform',
        'vsphere',
        'proxmox',
        'citrix',
        'oracle vm',
        'bochs',
        'virtual pc',
        'cloud pc',
        'shadow pc',
        'amazon ec2',
        'google compute engine',
        'azure virtual machine',
        'bhyve',
        'virtual desktop',
        'sandbox',
    ]

    @staticmethod
    def check_model():
        if os.name == 'nt':
            model = None
            explanation = None

            try:
                cmd = 'Get-CimInstance Win32_ComputerSystem | Select-Object Model'
                output = subprocess.check_output(['powershell', '-Command', cmd], shell=True).decode()
                model = output.split('\n')[3].strip()
                
                if model.lower() in Specs._MODELS:
                    score = 0
                    explanation = "MODEL has been linked to a virtual machine."
                else:
                    score = 5

            except Exception as e:
                score = 5
                explanation = f"Something went wrong, so giving benefit of the doubt. Considering this test successful.\nexception: {e}"

            description = f"MODEL is {model}."

            return score, description, explanation
        else:  # Unix/Linux/Mac
            return 5, "MODEL is None.", "This test can only be run on Windows. Considering this test successful."

    # TODO add all known manufacturers of virtual machines
    _MANUFACTURER = [
        'innotek',
        'vmware',
        'qemu',
        'xen',
        'parallels',
        'oracle',
        'citrix',
        'red hat',
        'proxmox',
        'amazon web services',
        'google cloud',
        'microsoft azure',
        'virtualbox',
        'docker',
        'nutanix',
        'cloud',
        'vagrant',
        'kubernetes',
        'openstack',
        'digital ocean',
        'linode',
        'vultr',
        'ibm cloud',
        'alibaba cloud',
        'huawei cloud',
        'tencent cloud',
    ]

    @staticmethod
    def check_manufacturer():
        if os.name == 'nt':
            manufacturer = None
            explanation = None

            try:
                cmd = 'Get-CimInstance Win32_ComputerSystem | Select-Object Manufacturer'
                output = subprocess.check_output(['powershell', '-Command', cmd], shell=True).decode()
                manufacturer = output.split('\n')[3].strip()

                if manufacturer.lower() in Specs._MANUFACTURER:
                    score = 0
                    explanation = "MANUFACTURER has been linked to a virtual machine."
                else:
                    score = 5

            except Exception as e:
                score = 5
                explanation = f"Something went wrong, so giving benefit of the doubt. Considering this test successful.\nexception: {e}"

            description = f"MANUFACTURER is {manufacturer}."
            return score, description, explanation

        else:
            return 5, "MANUFACTURER is None.", "This test can only be run on Windows. Considering this test successful."
