using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Web.Script.Serialization;
using Microsoft.Win32;

[assembly: AssemblyTitle("Agente TI Extrema Windows")]
[assembly: AssemblyCompany("Prefeitura Municipal de Extrema")]
[assembly: AssemblyProduct("Inventario Automatico de Computadores")]
[assembly: AssemblyVersion("1.0.1.0")]
[assembly: AssemblyFileVersion("1.0.1.0")]

namespace PrefeituraExtrema.AgenteTI
{
    internal sealed class AgentConfig
    {
        public string Server { get; set; }
        public string AgentId { get; set; }
        public string Token { get; set; }
        public string Patrimonio { get; set; }
        public string Responsavel { get; set; }
        public string Setor { get; set; }
        public int SyncIntervalMinutes { get; set; }
        public string LastSyncUtc { get; set; }
    }

    internal static class Program
    {
        private const string Version = "1.0.1";
        private const int VersionCode = 2;
        private static readonly JavaScriptSerializer Json = new JavaScriptSerializer { MaxJsonLength = 262144 };
        private static readonly string DataDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "PrefeituraExtrema", "AgenteTIWindows");
        private static readonly string ConfigFile = Path.Combine(DataDirectory, "config.dat");
        private static readonly string LogFile = Path.Combine(DataDirectory, "agent.log");

        private static int Main(string[] args)
        {
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            string command = args.Length == 0 ? "status" : args[0].Trim().ToLowerInvariant();
            try
            {
                if (command == "activate") return Activate();
                if (command == "sync") return Sync(false, false);
                if (command == "scheduled") return Sync(false, true);
                if (command == "diagnostic") return Diagnostic();
                if (command == "collect") { Console.WriteLine(Json.Serialize(Collect(null))); return 0; }
                if (command == "status") return Status();
                PrintUsage(); return 2;
            }
            catch (Exception error)
            {
                Log("ERRO " + command + ": " + SafeMessage(error.ToString()));
                Console.Error.WriteLine("Falha: " + SafeMessage(error.Message));
                return 1;
            }
        }

        private static int Activate()
        {
            Console.WriteLine("PREFEITURA DE EXTREMA - AGENTE TI WINDOWS");
            Console.WriteLine("Ativacao institucional. Execute somente em equipamento autorizado.\n");
            string server = PromptDefault("Servidor", "https://extrema.dev.br/manutencao_ti/api/computadores");
            if (!server.StartsWith("https://", StringComparison.OrdinalIgnoreCase) && !server.StartsWith("http://localhost", StringComparison.OrdinalIgnoreCase) && !server.StartsWith("http://127.0.0.1", StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException("O servidor deve utilizar HTTPS.");
            server = server.TrimEnd('/');
            string code = PromptSecret("Codigo temporario").ToUpperInvariant();
            string patrimonio = Prompt("Patrimonio");
            string responsavel = Prompt("Responsavel");
            string setor = Prompt("Setor");
            if (patrimonio.Length == 0 || responsavel.Length == 0 || setor.Length == 0) throw new InvalidOperationException("Patrimonio, responsavel e setor sao obrigatorios.");
            Dictionary<string, object> payload = Collect(null);
            payload["activation_code"] = code; payload["patrimonio"] = patrimonio; payload["responsavel_nome"] = responsavel; payload["setor"] = setor;
            Dictionary<string, object> response = Post(server + "/registrar.php", payload, null);
            AgentConfig config = new AgentConfig { Server = server, AgentId = GetString(response, "agent_id"), Token = GetString(response, "token"), Patrimonio = patrimonio, Responsavel = responsavel, Setor = setor, SyncIntervalMinutes = GetInt(response, "sync_interval_min", 30) };
            if (config.AgentId.Length == 0 || config.Token.Length != 64) throw new InvalidOperationException("O servidor nao retornou credenciais validas.");
            SaveConfig(config); Log("ATIVADO " + config.AgentId + " em " + Environment.MachineName); Console.WriteLine("\nAtivacao concluida: " + config.AgentId);
            try { return Sync(true, false); }
            catch (Exception firstSyncError) { Log("PRIMEIRA SINCRONIZACAO PENDENTE: " + SafeMessage(firstSyncError.ToString())); Console.WriteLine("A primeira coleta ficou pendente e sera repetida pela tarefa automatica."); return 0; }
        }

        private static int Sync(bool firstRun, bool respectInterval)
        {
            AgentConfig config = LoadConfig(); DateTime lastSync;
            if (respectInterval && DateTime.TryParse(config.LastSyncUtc, out lastSync) && DateTime.UtcNow - lastSync.ToUniversalTime() < TimeSpan.FromMinutes(Math.Max(15, config.SyncIntervalMinutes) - 1)) return 0;
            Dictionary<string, object> payload = Collect(config); payload["patrimonio"] = config.Patrimonio; payload["responsavel_nome"] = config.Responsavel; payload["setor"] = config.Setor;
            Dictionary<string, object> response = Post(config.Server + "/sincronizar.php", payload, config);
            config.SyncIntervalMinutes = GetInt(response, "sync_interval_min", config.SyncIntervalMinutes <= 0 ? 30 : config.SyncIntervalMinutes); config.LastSyncUtc = DateTime.UtcNow.ToString("o"); SaveConfig(config);
            Log("SINCRONIZACAO OK " + config.AgentId); if (firstRun || Environment.UserInteractive) Console.WriteLine("Sincronizacao concluida em " + DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss")); return 0;
        }

        private static int Diagnostic()
        {
            AgentConfig config = LoadConfig(); Dictionary<string, object> response = Post(config.Server + "/status.php", new Dictionary<string, object>(), config);
            Console.WriteLine("Servidor: acessivel\nAgente: " + config.AgentId + "\nStatus: " + GetString(response, "status") + "\nVersao: " + Version); return 0;
        }

        private static int Status()
        {
            if (!File.Exists(ConfigFile)) { Console.WriteLine("Agente ainda nao ativado. Execute: Agente-TI-Extrema-Windows.exe activate"); return 3; }
            AgentConfig config = LoadConfig(); Console.WriteLine("Agente TI Extrema Windows\nAgent ID: " + config.AgentId + "\nServidor: " + config.Server + "\nPatrimonio: " + config.Patrimonio + "\nVersao: " + Version + "\nConfiguracao protegida: " + ConfigFile);
            if (File.Exists(LogFile)) Console.WriteLine("Ultimo registro: " + File.ReadLines(LogFile).LastOrDefault()); return 0;
        }

        private static Dictionary<string, object> Collect(AgentConfig config)
        {
            Dictionary<string, object> data = new Dictionary<string, object>();
            Dictionary<string, object> computer = WmiFirst("root\\cimv2", "SELECT Manufacturer,Model,TotalPhysicalMemory,PCSystemType FROM Win32_ComputerSystem");
            Dictionary<string, object> bios = WmiFirst("root\\cimv2", "SELECT SerialNumber,SMBIOSBIOSVersion FROM Win32_BIOS");
            Dictionary<string, object> os = WmiFirst("root\\cimv2", "SELECT Caption,Version,BuildNumber,OSArchitecture,InstallDate,LastBootUpTime,FreePhysicalMemory,TotalVisibleMemorySize FROM Win32_OperatingSystem");
            Dictionary<string, object> cpu = WmiFirst("root\\cimv2", "SELECT Name,NumberOfCores,NumberOfLogicalProcessors FROM Win32_Processor");
            Dictionary<string, object> disk = WmiFirst("root\\cimv2", "SELECT Size,FreeSpace FROM Win32_LogicalDisk WHERE DeviceID='C:'");
            Dictionary<string, object> drive = WmiFirst("root\\cimv2", "SELECT Model,MediaType FROM Win32_DiskDrive WHERE Index=0");
            Dictionary<string, object> video = WmiFirst("root\\cimv2", "SELECT Name FROM Win32_VideoController");
            if (Value(computer, "Manufacturer").Length == 0) computer["Manufacturer"] = ReadRegistry(@"HARDWARE\DESCRIPTION\System\BIOS", "SystemManufacturer", RegistryHive.LocalMachine);
            if (Value(computer, "Model").Length == 0) computer["Model"] = ReadRegistry(@"HARDWARE\DESCRIPTION\System\BIOS", "SystemProductName", RegistryHive.LocalMachine);
            if (Value(bios, "SerialNumber").Length == 0) bios["SerialNumber"] = ReadRegistry(@"HARDWARE\DESCRIPTION\System\BIOS", "SystemSerialNumber", RegistryHive.LocalMachine);
            if (Value(bios, "SMBIOSBIOSVersion").Length == 0) bios["SMBIOSBIOSVersion"] = ReadRegistry(@"HARDWARE\DESCRIPTION\System\BIOS", "BIOSVersion", RegistryHive.LocalMachine);
            if (Value(os, "Caption").Length == 0) os["Caption"] = ReadRegistry(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ProductName", RegistryHive.LocalMachine);
            if (Value(os, "Version").Length == 0) os["Version"] = ReadRegistry(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "DisplayVersion", RegistryHive.LocalMachine);
            if (Value(os, "BuildNumber").Length == 0) os["BuildNumber"] = ReadRegistry(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentBuildNumber", RegistryHive.LocalMachine);
            if (Value(os, "OSArchitecture").Length == 0) os["OSArchitecture"] = Environment.Is64BitOperatingSystem ? "64 bits" : "32 bits";
            if (Value(cpu, "Name").Length == 0) cpu["Name"] = Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER") ?? "";
            if (LongValue(cpu, "NumberOfLogicalProcessors") == 0) cpu["NumberOfLogicalProcessors"] = Environment.ProcessorCount;
            if (LongValue(cpu, "NumberOfCores") == 0) cpu["NumberOfCores"] = Environment.ProcessorCount;
            string serial = Value(bios, "SerialNumber"); string machineGuid = ReadMachineGuid(); string fingerprint = Sha256(machineGuid + "|" + serial + "|" + Environment.MachineName);
            NetworkInterface network = NetworkInterface.GetAllNetworkInterfaces().FirstOrDefault(n => n.OperationalStatus == OperationalStatus.Up && n.NetworkInterfaceType != NetworkInterfaceType.Loopback && n.GetIPProperties().UnicastAddresses.Any(a => a.Address.AddressFamily == AddressFamily.InterNetwork));
            string ipv4 = network == null ? "" : network.GetIPProperties().UnicastAddresses.First(a => a.Address.AddressFamily == AddressFamily.InterNetwork).Address.ToString();
            string mac = network == null ? "" : string.Join(":", Enumerable.Range(0, network.GetPhysicalAddress().GetAddressBytes().Length).Select(i => network.GetPhysicalAddress().GetAddressBytes()[i].ToString("X2")).ToArray());
            DateTime boot = WmiDate(Value(os, "LastBootUpTime")); DateTime installed = WmiDate(Value(os, "InstallDate"));
            if (boot == DateTime.MinValue) boot = DateTime.Now.AddMilliseconds(-Math.Max(0, Environment.TickCount));
            if (installed == DateTime.MinValue) { long epoch; if (long.TryParse(ReadRegistry(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "InstallDate", RegistryHive.LocalMachine), out epoch)) installed = new DateTime(1970,1,1,0,0,0,DateTimeKind.Utc).AddSeconds(epoch).ToLocalTime(); }
            long totalRam = LongValue(os, "TotalVisibleMemorySize") / 1024; if (totalRam <= 0) totalRam = LongValue(computer, "TotalPhysicalMemory") / 1048576; MEMORYSTATUSEX memory = new MEMORYSTATUSEX(); if (GlobalMemoryStatusEx(memory)) { if (totalRam <= 0) totalRam = (long)(memory.ullTotalPhys / 1048576UL); if (LongValue(os, "FreePhysicalMemory") == 0) os["FreePhysicalMemory"] = (memory.ullAvailPhys / 1024UL).ToString(); }
            if (LongValue(disk, "Size") == 0) { try { DriveInfo systemDrive = new DriveInfo(Path.GetPathRoot(Environment.SystemDirectory)); disk["Size"] = systemDrive.TotalSize; disk["FreeSpace"] = systemDrive.AvailableFreeSpace; } catch { } }
            int pcSystemType = (int)LongValue(computer, "PCSystemType"); string type = pcSystemType == 2 ? "NOTEBOOK" : "DESKTOP";
            data["fingerprint_hash"] = fingerprint; data["machine_guid_hash"] = Sha256(machineGuid); data["tipo"] = type; data["hostname"] = Environment.MachineName; data["dominio"] = Environment.UserDomainName; data["ultimo_usuario"] = Environment.UserDomainName + "\\" + Environment.UserName;
            data["fabricante"] = Value(computer, "Manufacturer"); data["modelo"] = Value(computer, "Model"); data["numero_serie"] = serial; data["bios_version"] = Value(bios, "SMBIOSBIOSVersion");
            data["sistema_operacional"] = Value(os, "Caption"); data["os_version"] = Value(os, "Version"); data["os_build"] = Value(os, "BuildNumber"); data["arquitetura"] = Value(os, "OSArchitecture"); data["instalado_em"] = installed == DateTime.MinValue ? "" : installed.ToString("yyyy-MM-dd HH:mm:ss"); data["ultimo_boot"] = boot == DateTime.MinValue ? "" : boot.ToString("yyyy-MM-dd HH:mm:ss"); data["uptime_seconds"] = boot == DateTime.MinValue ? 0 : Math.Max(0, (long)(DateTime.Now - boot).TotalSeconds);
            data["processador"] = Value(cpu, "Name"); data["cpu_cores"] = (int)LongValue(cpu, "NumberOfCores"); data["cpu_logical"] = (int)LongValue(cpu, "NumberOfLogicalProcessors"); data["ram_total_mb"] = (int)Math.Min(int.MaxValue, totalRam); data["ram_livre_mb"] = (int)Math.Min(int.MaxValue, LongValue(os, "FreePhysicalMemory") / 1024);
            data["armazenamento_total_mb"] = (int)Math.Min(int.MaxValue, LongValue(disk, "Size") / 1048576); data["armazenamento_livre_mb"] = (int)Math.Min(int.MaxValue, LongValue(disk, "FreeSpace") / 1048576); data["armazenamento_tipo"] = (Value(drive, "MediaType") + " " + Value(drive, "Model")).Trim(); data["gpu"] = Value(video, "Name");
            data["ipv4"] = ipv4; data["mac_address"] = mac; data["rede_tipo"] = network == null ? "DESCONECTADO" : network.NetworkInterfaceType.ToString(); data["bitlocker_status"] = BitLocker(); data["antivirus"] = Antivirus(); data["agent_version"] = Version; data["agent_version_code"] = VersionCode; data["coletado_em"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); return data;
        }

        private static Dictionary<string, object> Post(string url, Dictionary<string, object> payload, AgentConfig config)
        {
            using (HttpClient client = new HttpClient())
            using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url))
            {
                client.Timeout = TimeSpan.FromSeconds(40); request.Headers.UserAgent.ParseAdd("AgenteTIExtrema-Windows/" + Version); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                if (config != null) { request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", config.Token); request.Headers.Add("X-Agent-Token", config.Token); request.Headers.Add("X-Agent-Id", config.AgentId); request.Headers.Add("X-Timestamp", UnixTime().ToString()); request.Headers.Add("X-Nonce", RandomHex(32)); }
                request.Content = new StringContent(Json.Serialize(payload), Encoding.UTF8, "application/json"); HttpResponseMessage response = client.SendAsync(request).GetAwaiter().GetResult(); string body = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); Dictionary<string, object> parsed;
                try { parsed = Json.Deserialize<Dictionary<string, object>>(body); } catch { throw new InvalidOperationException("Resposta invalida do servidor (HTTP " + (int)response.StatusCode + ")."); }
                if (parsed == null) throw new InvalidOperationException("O servidor retornou uma resposta JSON vazia (HTTP " + (int)response.StatusCode + ").");
                if (!response.IsSuccessStatusCode || !GetBool(parsed, "ok")) throw new InvalidOperationException(GetString(parsed, "message").Length > 0 ? GetString(parsed, "message") : "Servidor retornou HTTP " + (int)response.StatusCode + "."); return parsed;
            }
        }

        private static void SaveConfig(AgentConfig config)
        {
            Directory.CreateDirectory(DataDirectory); byte[] plain = Encoding.UTF8.GetBytes(Json.Serialize(config)); byte[] protectedBytes = ProtectedData.Protect(plain, Encoding.UTF8.GetBytes("PrefeituraExtrema.AgentTI.v1"), DataProtectionScope.LocalMachine); File.WriteAllBytes(ConfigFile, protectedBytes);
        }

        private static AgentConfig LoadConfig()
        {
            if (!File.Exists(ConfigFile)) throw new InvalidOperationException("Agente ainda nao foi ativado."); byte[] protectedBytes = File.ReadAllBytes(ConfigFile); byte[] plain = ProtectedData.Unprotect(protectedBytes, Encoding.UTF8.GetBytes("PrefeituraExtrema.AgentTI.v1"), DataProtectionScope.LocalMachine); AgentConfig config = Json.Deserialize<AgentConfig>(Encoding.UTF8.GetString(plain)); if (config == null || string.IsNullOrWhiteSpace(config.Token)) throw new InvalidOperationException("Configuracao local invalida."); return config;
        }

        private static Dictionary<string, object> WmiFirst(string scope, string query)
        {
            Dictionary<string, object> result = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); try { using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query)) foreach (ManagementObject item in searcher.Get()) { foreach (PropertyData property in item.Properties) result[property.Name] = property.Value; break; } } catch { } return result;
        }
        private static string BitLocker() { try { Dictionary<string, object> x = WmiFirst("root\\CIMV2\\Security\\MicrosoftVolumeEncryption", "SELECT ProtectionStatus FROM Win32_EncryptableVolume WHERE DriveLetter='C:'"); return LongValue(x, "ProtectionStatus") == 1 ? "PROTEGIDO" : "NAO PROTEGIDO"; } catch { return "NAO DISPONIVEL"; } }
        private static string Antivirus() { try { Dictionary<string, object> x = WmiFirst("root\\SecurityCenter2", "SELECT displayName FROM AntiVirusProduct"); string value = Value(x, "displayName"); return value.Length == 0 ? "NAO INFORMADO" : value; } catch { return "NAO DISPONIVEL"; } }
        private static string ReadMachineGuid() { using (RegistryKey key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64).OpenSubKey(@"SOFTWARE\Microsoft\Cryptography")) return Convert.ToString(key == null ? "" : key.GetValue("MachineGuid")) ?? ""; }
        private static string ReadRegistry(string path, string name, RegistryHive hive) { try { using (RegistryKey key = RegistryKey.OpenBaseKey(hive, RegistryView.Registry64).OpenSubKey(path)) return Convert.ToString(key == null ? "" : key.GetValue(name)) ?? ""; } catch { return ""; } }
        private static DateTime WmiDate(string value) { try { return value.Length > 0 ? ManagementDateTimeConverter.ToDateTime(value) : DateTime.MinValue; } catch { return DateTime.MinValue; } }
        private static string Value(Dictionary<string, object> values, string key) { if (values == null) return ""; object value; return values.TryGetValue(key, out value) && value != null ? (Convert.ToString(value) ?? "").Trim() : ""; }
        private static long LongValue(Dictionary<string, object> values, string key) { long parsed; return long.TryParse(Value(values, key), out parsed) ? parsed : 0; }
        private static string GetString(Dictionary<string, object> values, string key) { return Value(values, key); }
        private static int GetInt(Dictionary<string, object> values, string key, int fallback) { int parsed; return int.TryParse(Value(values, key), out parsed) ? parsed : fallback; }
        private static bool GetBool(Dictionary<string, object> values, string key) { if (values == null) return false; object value; if (!values.TryGetValue(key, out value) || value == null) return false; bool parsed; return bool.TryParse(Convert.ToString(value), out parsed) && parsed; }
        private static string Prompt(string label) { Console.Write(label + ": "); return (Console.ReadLine() ?? "").Trim(); }
        private static string PromptDefault(string label, string defaultValue) { Console.Write(label + " [" + defaultValue + "]: "); string value = (Console.ReadLine() ?? "").Trim(); return value.Length == 0 ? defaultValue : value; }
        private static string PromptSecret(string label) { Console.Write(label + ": "); StringBuilder value = new StringBuilder(); ConsoleKeyInfo key; while ((key = Console.ReadKey(true)).Key != ConsoleKey.Enter) { if (key.Key == ConsoleKey.Backspace && value.Length > 0) { value.Length--; continue; } if (!char.IsControl(key.KeyChar)) value.Append(key.KeyChar); } Console.WriteLine(); return value.ToString().Trim(); }
        private static string Sha256(string value) { using (SHA256 sha = SHA256.Create()) return BitConverter.ToString(sha.ComputeHash(Encoding.UTF8.GetBytes(value ?? ""))).Replace("-", "").ToLowerInvariant(); }
        private static string RandomHex(int bytes) { byte[] data = new byte[bytes]; using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) rng.GetBytes(data); return BitConverter.ToString(data).Replace("-", "").ToLowerInvariant(); }
        private static long UnixTime() { return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds; }
        private static string SafeMessage(string message) { if (string.IsNullOrWhiteSpace(message)) return "Erro nao especificado."; return message.Length > 500 ? message.Substring(0, 500) : message; }
        private static void Log(string message) { try { Directory.CreateDirectory(DataDirectory); File.AppendAllText(LogFile, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " " + message.Replace("\r", " ").Replace("\n", " ") + Environment.NewLine, Encoding.UTF8); string[] lines = File.ReadAllLines(LogFile); if (lines.Length > 1000) File.WriteAllLines(LogFile, lines.Skip(lines.Length - 600).ToArray(), Encoding.UTF8); } catch { } }
        private static void PrintUsage() { Console.WriteLine("Comandos: activate | sync | scheduled | diagnostic | status | collect"); }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private sealed class MEMORYSTATUSEX
        {
            public uint dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX)); public uint dwMemoryLoad; public ulong ullTotalPhys; public ulong ullAvailPhys; public ulong ullTotalPageFile; public ulong ullAvailPageFile; public ulong ullTotalVirtual; public ulong ullAvailVirtual; public ulong ullAvailExtendedVirtual;
        }
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GlobalMemoryStatusEx([In, Out] MEMORYSTATUSEX buffer);
    }
}
