using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Http;

namespace MossyUpdater.Services
{
    public class DownloadService : IDownloadService
    {
        private readonly HttpClient _httpClient;
        private readonly ILogger _logger;
        private const int MaxRetries = 3;
        private static readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(2);

        public DownloadService(HttpClient httpClient, ILogger logger)
        {
            _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        public async Task<DownloadResult> DownloadFileAsync(string url, string destinationPath, IProgress<DownloadProgress>? progress = null, CancellationToken cancellationToken = default)
        {
            var stopwatch = Stopwatch.StartNew();
            var result = new DownloadResult();

            await _logger.LogInfoAsync($"Starting download from {url} to {destinationPath}");

            for (int attempt = 1; attempt <= MaxRetries; attempt++)
            {
                try
                {
                    if (attempt > 1)
                    {
                        progress?.Report(new DownloadProgress { Status = $"Retrying download (attempt {attempt}/{MaxRetries})..." });
                        await _logger.LogInfoAsync($"Retry attempt {attempt}/{MaxRetries}");
                        await Task.Delay(RetryDelay, cancellationToken);
                    }
                    else
                    {
                        progress?.Report(new DownloadProgress { Status = "Starting download..." });
                    }

                    using var response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
                    response.EnsureSuccessStatusCode();

                    var totalBytes = response.Content.Headers.ContentLength;
                    progress?.Report(new DownloadProgress { TotalBytes = totalBytes, Status = "Downloading..." });

                    var directory = Path.GetDirectoryName(destinationPath);
                    if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
                    {
                        Directory.CreateDirectory(directory);
                    }

                    using var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken);
                    using var fileStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 8192, useAsync: true);

                    var buffer = new byte[8192];
                    long totalBytesRead = 0;
                    int bytesRead;

                    while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
                    {
                        await fileStream.WriteAsync(buffer, 0, bytesRead, cancellationToken);
                        totalBytesRead += bytesRead;

                        progress?.Report(new DownloadProgress 
                        { 
                            BytesReceived = totalBytesRead, 
                            TotalBytes = totalBytes,
                            Status = $"Downloaded {FormatBytes(totalBytesRead)}" + (totalBytes.HasValue ? $" of {FormatBytes(totalBytes.Value)}" : "")
                        });
                    }

                    result.Success = true;
                    result.BytesDownloaded = totalBytesRead;
                    progress?.Report(new DownloadProgress 
                    { 
                        BytesReceived = totalBytesRead, 
                        TotalBytes = totalBytes,
                        Status = "Download completed successfully"
                    });

                    await _logger.LogInfoAsync($"Download completed successfully. Downloaded {FormatBytes(totalBytesRead)} in {stopwatch.Elapsed:mm\\:ss}");
                    break;
                }
                catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException && attempt < MaxRetries)
                {
                    await _logger.LogWarningAsync($"Timeout on attempt {attempt}: {ex.Message}");
                    continue;
                }
                catch (OperationCanceledException)
                {
                    result.ErrorMessage = "Download was cancelled";
                    progress?.Report(new DownloadProgress { Status = "Download cancelled" });
                    await _logger.LogInfoAsync("Download was cancelled by user");
                    
                    if (File.Exists(destinationPath))
                    {
                        try { File.Delete(destinationPath); } catch { }
                    }
                    break;
                }
                catch (HttpRequestException ex) when (IsRetryableHttpError(ex) && attempt < MaxRetries)
                {
                    await _logger.LogWarningAsync($"Retryable error on attempt {attempt}: {ex.Message}");
                    continue;
                }
                catch (Exception ex)
                {
                    result.ErrorMessage = GetFriendlyErrorMessage(ex);
                    progress?.Report(new DownloadProgress { Status = $"Error: {result.ErrorMessage}" });
                    await _logger.LogErrorAsync($"Download failed on attempt {attempt}", ex);
                    
                    if (File.Exists(destinationPath))
                    {
                        try { File.Delete(destinationPath); } catch { }
                    }

                    if (attempt >= MaxRetries)
                        break;
                }
            }

            stopwatch.Stop();
            result.Duration = stopwatch.Elapsed;
            return result;
        }

        public async Task<FileUpdateInfo> CheckForUpdatesAsync(string url, string localFilePath, CancellationToken cancellationToken = default)
        {
            var updateInfo = new FileUpdateInfo();

            try
            {
                var request = new HttpRequestMessage(HttpMethod.Head, url);
                using var response = await _httpClient.SendAsync(request, cancellationToken);
                
                if (response.IsSuccessStatusCode)
                {
                    updateInfo.RemoteLastModified = response.Content.Headers.LastModified?.DateTime;
                    updateInfo.RemoteSize = response.Content.Headers.ContentLength;

                    if (File.Exists(localFilePath))
                    {
                        var localFileInfo = new FileInfo(localFilePath);
                        updateInfo.LocalLastModified = localFileInfo.LastWriteTime;
                        updateInfo.LocalSize = localFileInfo.Length;

                        updateInfo.UpdateAvailable = 
                            updateInfo.RemoteLastModified.HasValue && 
                            updateInfo.RemoteLastModified > updateInfo.LocalLastModified ||
                            (updateInfo.RemoteSize.HasValue && updateInfo.RemoteSize != updateInfo.LocalSize);
                    }
                    else
                    {
                        updateInfo.UpdateAvailable = true;
                    }
                }
                else
                {
                    updateInfo.ErrorMessage = $"Failed to check for updates: {response.StatusCode}";
                }
            }
            catch (Exception ex)
            {
                updateInfo.ErrorMessage = $"Error checking for updates: {ex.Message}";
            }

            return updateInfo;
        }

        public void UnblockFile(string filePath)
        {
            try
            {
                var zoneIdentifier = filePath + ":Zone.Identifier";
                if (File.Exists(zoneIdentifier))
                {
                    File.Delete(zoneIdentifier);
                }
            }
            catch
            {
            }
        }

        private static string FormatBytes(long bytes)
        {
            string[] suffixes = { "B", "KB", "MB", "GB", "TB" };
            int counter = 0;
            decimal number = bytes;
            
            while (Math.Round(number / 1024) >= 1)
            {
                number /= 1024;
                counter++;
            }
            
            return $"{number:n1} {suffixes[counter]}";
        }

        private static bool IsRetryableHttpError(HttpRequestException ex)
        {
            return ex.Message.Contains("timeout", StringComparison.OrdinalIgnoreCase) ||
                   ex.Message.Contains("connection", StringComparison.OrdinalIgnoreCase) ||
                   ex.Message.Contains("network", StringComparison.OrdinalIgnoreCase);
        }

        private static string GetFriendlyErrorMessage(Exception ex)
        {
            return ex switch
            {
                HttpRequestException httpEx when httpEx.Message.Contains("404") => "File not found on server",
                HttpRequestException httpEx when httpEx.Message.Contains("403") => "Access denied to file",
                HttpRequestException httpEx when httpEx.Message.Contains("timeout") => "Download timed out",
                UnauthorizedAccessException => "Access denied to destination folder",
                DirectoryNotFoundException => "Destination folder does not exist",
                IOException => "File operation failed",
                _ => ex.Message
            };
        }
    }
}