using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Aliyun.OSS;
using Aliyun.OSS.Common;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace CheckDownload
{
    public partial class Update : Form
    {
        // MD5文件名称
        private const string Md5File = "md5.json";
        // 阿里云OSS访问地址
        private const string OssEndpoint = "oss-cn-hongkong.aliyuncs.com";
        // 阿里云OSS存储空间名称
        private const string OssBucketName = "sxho-hk";
        // 阿里云OSS访问密钥ID
        private const string OssAccessKeyId = "LTAI5tBc8BgooVrHrzfEeg1q";
        // 阿里云OSS访问密钥Secret
        private const string OssAccessKeySecret = "nMvSp0UqzodTzKImuAMK1a1bkSks5O";
        // 用于存储下载的文件数据
        private Dictionary<string, (byte[] Data, string ExpectedMd5)> _downloadedFiles = new Dictionary<string, (byte[], string)>();
        // 已完成的下载数量
        private int _completedCount = 0;
        // 总下载数量
        private int _totalCount = 0;

        // 初始化窗体
        public Update()
        {
            InitializeComponent();
            ConfigureProgressBar();
        }

        // 配置进度条
        private void ConfigureProgressBar()
        {
            Update_Pro.Minimum = 0;
            Update_Pro.Maximum = 100;
            Update_Pro.Value = 0;
            Update_Pro.Step = 1;
        }

        private void UpdateProgressValue(int percentage)
        {
            if (percentage < 0) percentage = 0;
            if (percentage > 100) percentage = 100;

            if (this.InvokeRequired)
            {
                this.Invoke((MethodInvoker)delegate
                {
                    Update_Pro.Value = Math.Min(Math.Max(percentage, Update_Pro.Minimum), Update_Pro.Maximum);
                    Application.DoEvents();
                });
            }
            else
            {
                Update_Pro.Value = Math.Min(Math.Max(percentage, Update_Pro.Minimum), Update_Pro.Maximum);
                Application.DoEvents();
            }
        }

        private void UpdateStatus(string message)
        {
            if (this.InvokeRequired)
            {
                this.Invoke((MethodInvoker)delegate { Status_Box.Text = message; });
            }
            else
            {
                Status_Box.Text = message;
            }
        }

        public async void Update_Load(object sender, EventArgs e)
        {
            PositionFormToBottomRight();
            await UpdateFile();
        }

        private void PositionFormToBottomRight()
        {
            Rectangle workingArea = Screen.GetWorkingArea(this);
            this.Location = new Point(workingArea.Right - this.Width, workingArea.Bottom - this.Height);
        }

        private async Task UpdateFile()
        {
            try
            {
                const int totalSteps = 4;
                int currentStep = 0;

                UpdateStatus("下载在线MD5文件并读取...");
                OssClient client = new OssClient(OssEndpoint, OssAccessKeyId, OssAccessKeySecret);
                var onlineData = ReadOnlineMd5File(client, OssBucketName, Md5File);
                if (!ValidateOnlineData(onlineData.Version, onlineData.Md5, onlineData.Data)) return;

                UpdateStatus("比较本地和在线MD5文件...");
                var compareResult = CompareMd5Data(onlineData.Data);
                if (compareResult.Count == 0)
                {
                    UpdateStatus("所有文件都是最新的,无需更新");
                    UpdateProgressValue(100);
                    await Task.Delay(2000);
                    this.Close();
                    return;
                }

                UpdateStatus("下载并验证文件...");
                _totalCount = compareResult.Count;
                await DownloadAndVerifyFiles(compareResult);
                UpdateProgressValue(100);

                UpdateStatus("更新完成");
                await Task.Delay(1000);
                this.Close();
            }
            catch (Exception ex)
            {
                UpdateStatus($"更新失败: {ex.Message}");
                await Task.Delay(3000);
                this.Close();
            }
        }

        private (string Version, string Md5, JObject Data) ReadOnlineMd5File(OssClient client, string bucket, string key)
        {
            try
            {
                var obj = client.GetObject(bucket, key);
                using (var reader = new StreamReader(obj.Content))
                {
                    string json = reader.ReadToEnd();
                    var parsed = JObject.Parse(json);
                    string version = parsed["version"]?.ToString();
                    var data = (JObject)parsed["data"];
                    string jsonMd5 = CalculateMD5(json);
                    return (version, jsonMd5, data);
                }
            }
            catch (Exception ex) when (ex is OssException || ex is JsonException)
            {
                UpdateStatus($"读取在线MD5文件失败: {ex.Message}");
                return (null, null, null);
            }
        }

        private bool ValidateOnlineData(string version, string md5, JObject data)
        {
            if (string.IsNullOrEmpty(version) || string.IsNullOrEmpty(md5) || data == null)
            {
                UpdateStatus("在线MD5文件无效");
                return false;
            }
            return true;
        }

        private Dictionary<string, string> CompareMd5Data(JObject onlineData, string currentPath = "")
        {
            var differences = new Dictionary<string, string>();

            foreach (var onlineProperty in onlineData.Properties())
            {
                string key = onlineProperty.Name;
                JToken onlineValue = onlineProperty.Value;
                string fullPath = string.IsNullOrEmpty(currentPath) ? key : $"{currentPath}/{key}";

                if (onlineValue.Type == JTokenType.String)
                {
                    string expectedMd5 = onlineValue.ToString();
                    if (!File.Exists(fullPath))
                    {
                        if (!differences.ContainsKey(fullPath))
                        {
                            differences[fullPath] = expectedMd5;
                        }
                    }
                    else
                    {
                        string localMd5 = CalculateMD5(File.ReadAllBytes(fullPath));
                        if (localMd5 != expectedMd5)
                        {
                            if (!differences.ContainsKey(fullPath))
                            {
                                differences[fullPath] = expectedMd5;
                            }
                        }
                    }
                }
                else if (onlineValue.Type == JTokenType.Object)
                {
                    var subDifferences = CompareMd5Data((JObject)onlineValue, fullPath);
                    foreach (var diff in subDifferences)
                    {
                        if (!differences.ContainsKey(diff.Key))
                        {
                            differences[diff.Key] = diff.Value;
                        }
                    }
                }
            }
            return differences;
        }

        private async Task DownloadAndVerifyFiles(Dictionary<string, string> fileList)
        {
            try
            {
                OssClient client = new OssClient(OssEndpoint, OssAccessKeyId, OssAccessKeySecret);
                _totalCount = fileList.Count;
                _completedCount = 0;
                _downloadedFiles.Clear();

                var options = new ParallelOptions { MaxDegreeOfParallelism = 4 };

                await Task.Run(() =>
                {
                    Parallel.ForEach(fileList, options, (file, state) =>
                    {
                        string filePath = file.Key;
                        string expectedMd5 = file.Value;

                        try
                        {
                            string ossKey = $"File/{expectedMd5}";
                            var obj = client.GetObject(OssBucketName, ossKey);
                            using (var memoryStream = new MemoryStream())
                            {
                                obj.Content.CopyTo(memoryStream);
                                byte[] fileData = memoryStream.ToArray();

                                lock (_downloadedFiles)
                                {
                                    _downloadedFiles[filePath] = (fileData, expectedMd5);
                                }

                                Interlocked.Increment(ref _completedCount);
                                int progress = (int)((double)_completedCount / _totalCount * 95);
                                UpdateProgressValue(progress);
                                UpdateStatus($"已下载 {_completedCount}/{_totalCount}");
                            }
                        }
                        catch (Exception ex)
                        {
                            UpdateStatus($"下载文件 {filePath} 失败: {ex.Message}");
                        }
                    });
                });

                if (_downloadedFiles.Count != fileList.Count)
                {
                    throw new Exception($"部分文件下载失败,成功 {_downloadedFiles.Count}/{fileList.Count}");
                }

                VerifyAndSaveAllFiles();
            }
            catch (Exception ex)
            {
                UpdateStatus($"下载过程出错: {ex.Message}");
                throw;
            }
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetProcessHandleCount(IntPtr hProcess, out uint pdwHandleCount);

        private void VerifyAndSaveAllFiles()
        {
            UpdateStatus("正在校验文件...");
            var failedFiles = new List<string>();
            int processedCount = 0;
            int totalFiles = _downloadedFiles.Count;

            foreach (var item in _downloadedFiles)
            {
                string relativePath = item.Key;
                byte[] data = item.Value.Data;
                string expectedMd5 = item.Value.ExpectedMd5;

                try
                {
                    processedCount++;
                    UpdateStatus($"正在校验和保存文件 ({processedCount}/{totalFiles}): {relativePath}");

                    string actualMd5 = CalculateMD5(data);
                    if (actualMd5 != expectedMd5.ToLower())
                    {
                        throw new Exception($"MD5校验失败 (期望: {expectedMd5}, 实际: {actualMd5})");
                    }

                    string localPath = Path.Combine(Application.StartupPath, relativePath);
                    Directory.CreateDirectory(Path.GetDirectoryName(localPath));
                    
                    // 尝试保存文件,如果文件被占用则尝试解锁
                    if (!TrySaveFile(localPath, data))
                    {
                        // 如果无法解锁文件,则使用备用文件名保存
                        string backupPath = localPath + ".new";
                        File.WriteAllBytes(backupPath, data);
                        
                        // 创建一个批处理文件,在应用程序退出后替换文件
                        CreateReplaceScript(localPath, backupPath);
                        UpdateStatus($"文件 {relativePath} 正在被占用,将在程序重启后更新");
                    }

                    int percentage = 95 + (int)(processedCount * 5.0 / totalFiles);
                    UpdateProgressValue(Math.Min(100, percentage));
                }
                catch (Exception ex)
                {
                    UpdateStatus($"文件校验失败: {relativePath} - {ex.Message}");
                    failedFiles.Add(relativePath);
                }
            }

            foreach (var failedFile in failedFiles)
            {
                _downloadedFiles.Remove(failedFile);
            }

            if (failedFiles.Count > 0)
            {
                throw new Exception($"{failedFiles.Count}个文件校验失败");
            }
            else
            {
                UpdateStatus("所有文件校验和保存成功");
                UpdateProgressValue(100);
            }
        }
        
        private bool TrySaveFile(string filePath, byte[] data)
        {
            try
            {
                // 直接尝试写入文件
                File.WriteAllBytes(filePath, data);
                return true;
            }
            catch (IOException)
            {
                // 文件被占用,尝试解锁
                UpdateStatus($"文件 {Path.GetFileName(filePath)} 被占用,尝试解锁...");
                
                try
                {
                    // 尝试找出占用文件的进程
                    var processes = Process.GetProcesses();
                    foreach (var process in processes)
                    {
                        try
                        {
                            // 跳过当前进程
                            if (process.Id == Process.GetCurrentProcess().Id)
                                continue;
                                
                            // 检查进程是否可能占用了文件
                            string processName = process.ProcessName.ToLower();
                            if (processName.Contains("explorer") || 
                                processName.Contains("chrome") || 
                                processName.Contains("edge") || 
                                processName.Contains("firefox") ||
                                processName.Contains("iexplore") ||
                                processName.Contains("winword") ||
                                processName.Contains("excel") ||
                                processName.Contains("powerpnt") ||
                                processName.Contains("acrobat") ||
                                processName.Contains("reader"))
                            {
                                // 跳过常见的系统进程
                                continue;
                            }
                            
                            // 尝试获取进程句柄数量,如果句柄数量较多,可能正在使用文件
                            uint handleCount = 0;
                            if (GetProcessHandleCount(process.Handle, out handleCount) && handleCount > 0)
                            {
                                // 尝试关闭进程
                                UpdateStatus($"尝试关闭可能占用文件的进程: {process.ProcessName}");
                                process.CloseMainWindow();
                                
                                // 等待进程关闭
                                if (!process.WaitForExit(1000))
                                {
                                    // 如果进程没有响应,尝试强制结束
                                    process.Kill();
                                }
                            }
                        }
                        catch
                        {
                            // 忽略处理单个进程时的错误
                        }
                        finally
                        {
                            process.Dispose();
                        }
                    }
                    
                    // 再次尝试写入文件
                    Thread.Sleep(500); // 等待一段时间确保文件已释放
                    File.WriteAllBytes(filePath, data);
                    UpdateStatus($"文件 {Path.GetFileName(filePath)} 解锁成功并已更新");
                    return true;
                }
                catch
                {
                    // 如果仍然无法写入,返回失败
                    return false;
                }
            }
            catch (Exception ex)
            {
                UpdateStatus($"保存文件时发生错误: {ex.Message}");
                return false;
            }
        }
        
        private void CreateReplaceScript(string originalFile, string newFile)
        {
            try
            {
                string batchFilePath = Path.Combine(Application.StartupPath, "update_files.bat");
                string processId = Process.GetCurrentProcess().Id.ToString();
                string escapedOriginalFile = originalFile.Replace("\\", "\\\\");
                string escapedNewFile = newFile.Replace("\\", "\\\\");
                
                // 创建批处理文件内容
                StringBuilder batchContent = new StringBuilder();
                batchContent.AppendLine("@echo off");
                batchContent.AppendLine(":check_process");
                batchContent.AppendLine($"tasklist /FI \"PID eq {processId}\" 2>NUL | find /I \"{ processId }\" >NUL");
                batchContent.AppendLine("if %ERRORLEVEL% == 0 (");
                batchContent.AppendLine("    timeout /t 1 /nobreak > NUL");
                batchContent.AppendLine("    goto check_process");
                batchContent.AppendLine(")");
                batchContent.AppendLine("timeout /t 2 /nobreak > NUL");
                batchContent.AppendLine($"del \"{escapedOriginalFile}\" /f /q");
                batchContent.AppendLine($"move \"{escapedNewFile}\" \"{escapedOriginalFile}\"");
                batchContent.AppendLine("del \"%~f0\" /f /q");
                
                // 写入批处理文件
                bool fileExists = File.Exists(batchFilePath);
                using (StreamWriter writer = new StreamWriter(batchFilePath, fileExists))
                {
                    if (!fileExists)
                    {
                        writer.Write(batchContent.ToString());
                    }
                    else
                    {
                        // 如果文件已存在,只添加移动文件的命令
                        writer.WriteLine($"del \"{escapedOriginalFile}\" /f /q");
                        writer.WriteLine($"move \"{escapedNewFile}\" \"{escapedOriginalFile}\"");
                    }
                }
                
                // 如果是第一次创建批处理文件,启动它
                if (!fileExists)
                {
                    ProcessStartInfo startInfo = new ProcessStartInfo
                    {
                        FileName = "cmd.exe",
                        Arguments = $"/c start \"\" /min \"{batchFilePath}\"",
                        CreateNoWindow = true,
                        UseShellExecute = true,
                        WindowStyle = ProcessWindowStyle.Hidden
                    };
                    Process.Start(startInfo);
                }
            }
            catch (Exception ex)
            {
                UpdateStatus($"创建替换脚本时出错: {ex.Message}");
            }
        }

        private string CalculateMD5(string input)
        {
            using (var md5 = MD5.Create())
            {
                byte[] inputBytes = Encoding.UTF8.GetBytes(input);
                byte[] hashBytes = md5.ComputeHash(inputBytes);
                return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
            }
        }

        private string CalculateMD5(byte[] data)
        {
            using (var md5 = MD5.Create())
            {
                byte[] hashBytes = md5.ComputeHash(data);
                return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
            }
        }
    }
}