using System.IO.Compression;

namespace RR3CommunityServer.Services;

/// <summary>
/// Service for extracting and packing RR3 .z (ZLIB compressed) asset files
/// </summary>
public class AssetExtractionService
{
    private readonly ILogger<AssetExtractionService> _logger;

    public AssetExtractionService(ILogger<AssetExtractionService> logger)
    {
        _logger = logger;
    }

    /// <summary>
    /// Extracts a .z file (ZLIB compressed) to its original format
    /// </summary>
    public async Task<byte[]> ExtractZFileAsync(string filePath)
    {
        if (!File.Exists(filePath))
        {
            throw new FileNotFoundException($"File not found: {filePath}");
        }

        _logger.LogInformation($"Extracting .z file: {filePath}");

        var data = await File.ReadAllBytesAsync(filePath);
        var output = new List<byte>();
        int i = 0;
        int blocksFound = 0;

        // Scan for ZLIB magic bytes (0x78 followed by 0x9C, 0xDA, or 0x01)
        while (i < data.Length - 2)
        {
            if (data[i] == 0x78 && (data[i + 1] == 0x9C || data[i + 1] == 0xDA || data[i + 1] == 0x01))
            {
                try
                {
                    // Try to decompress from this position
                    using var compressed = new MemoryStream(data, i + 2, data.Length - i - 2);
                    using var deflate = new DeflateStream(compressed, CompressionMode.Decompress);
                    using var decompressed = new MemoryStream();
                    
                    await deflate.CopyToAsync(decompressed);
                    var block = decompressed.ToArray();

                    if (block.Length > 0)
                    {
                        output.AddRange(block);
                        _logger.LogDebug($"Block {blocksFound} found at offset 0x{i:X}");
                        blocksFound++;
                        i += block.Length;
                        continue;
                    }
                }
                catch
                {
                    // Not a valid ZLIB block, continue scanning
                }
            }
            i++;
        }

        if (blocksFound == 0)
        {
            throw new InvalidDataException("No valid ZLIB blocks found in file");
        }

        _logger.LogInformation($"Extracted {blocksFound} blocks, total size: {output.Count:N0} bytes");
        return output.ToArray();
    }

    /// <summary>
    /// Extracts a .z file and saves the result
    /// </summary>
    public async Task<string> ExtractZFileAsync(string inputPath, string? outputPath = null)
    {
        var extracted = await ExtractZFileAsync(inputPath);

        // Default output path: remove .z extension
        if (string.IsNullOrEmpty(outputPath))
        {
            outputPath = inputPath.EndsWith(".z", StringComparison.OrdinalIgnoreCase)
                ? inputPath[..^2]
                : $"{inputPath}.extracted";
        }

        // Backup existing file
        if (File.Exists(outputPath))
        {
            var backupPath = $"{outputPath}.bak";
            if (!File.Exists(backupPath))
            {
                File.Move(outputPath, backupPath);
                _logger.LogInformation($"Created backup: {backupPath}");
            }
        }

        await File.WriteAllBytesAsync(outputPath, extracted);
        _logger.LogInformation($"Saved extracted file: {outputPath}");

        return outputPath;
    }

    /// <summary>
    /// Packs a file with ZLIB compression to create .z format
    /// </summary>
    public async Task<byte[]> PackZFileAsync(byte[] data)
    {
        using var output = new MemoryStream();
        using (var deflate = new DeflateStream(output, CompressionLevel.Optimal))
        {
            // Write ZLIB header (0x78 0x9C for default compression)
            output.WriteByte(0x78);
            output.WriteByte(0x9C);
            
            await deflate.WriteAsync(data, 0, data.Length);
        }

        var compressed = output.ToArray();
        var ratio = (1.0 - (double)compressed.Length / data.Length) * 100;
        
        _logger.LogInformation($"Packed {data.Length:N0} bytes → {compressed.Length:N0} bytes (compression: {ratio:F1}%)");
        
        return compressed;
    }

    /// <summary>
    /// Packs a file with ZLIB compression
    /// </summary>
    public async Task<string> PackZFileAsync(string inputPath, string? outputPath = null)
    {
        if (!File.Exists(inputPath))
        {
            throw new FileNotFoundException($"File not found: {inputPath}");
        }

        _logger.LogInformation($"Packing file: {inputPath}");

        var data = await File.ReadAllBytesAsync(inputPath);
        var packed = await PackZFileAsync(data);

        // Default output: add .z extension
        outputPath ??= $"{inputPath}.z";

        await File.WriteAllBytesAsync(outputPath, packed);
        _logger.LogInformation($"Saved packed file: {outputPath}");

        return outputPath;
    }

    /// <summary>
    /// Batch extract multiple .z files from a directory
    /// </summary>
    public async Task<Dictionary<string, string>> BatchExtractAsync(string inputDirectory, string? outputDirectory = null)
    {
        if (!Directory.Exists(inputDirectory))
        {
            throw new DirectoryNotFoundException($"Directory not found: {inputDirectory}");
        }

        outputDirectory ??= Path.Combine(inputDirectory, "extracted");
        Directory.CreateDirectory(outputDirectory);

        var zFiles = Directory.GetFiles(inputDirectory, "*.z", SearchOption.AllDirectories);
        _logger.LogInformation($"Found {zFiles.Length} .z files in {inputDirectory}");

        var results = new Dictionary<string, string>();
        int success = 0;
        int failed = 0;

        foreach (var zFile in zFiles)
        {
            try
            {
                var fileName = Path.GetFileNameWithoutExtension(zFile);
                var outputPath = Path.Combine(outputDirectory, fileName);

                await ExtractZFileAsync(zFile, outputPath);
                results[zFile] = outputPath;
                success++;

                _logger.LogInformation($"[{success + failed}/{zFiles.Length}] ✅ Extracted: {Path.GetFileName(zFile)}");
            }
            catch (Exception ex)
            {
                failed++;
                _logger.LogError(ex, $"[{success + failed}/{zFiles.Length}] ❌ Failed: {Path.GetFileName(zFile)}");
                results[zFile] = $"ERROR: {ex.Message}";
            }
        }

        _logger.LogInformation($"Batch extraction complete: {success} success, {failed} failed");
        return results;
    }
}
