using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RR3CommunityServer.Data;
using RR3CommunityServer.Models;
using System.Security.Cryptography;
using System.IO.Compression;
using System.Text.Json;

namespace RR3CommunityServer.Pages;

[Authorize]
public class AssetsModel : PageModel
{
    private readonly RR3DbContext _context;
    private readonly IConfiguration _configuration;
    private readonly ILogger<AssetsModel> _logger;
    private readonly string _assetsBasePath;

    public AssetsModel(RR3DbContext context, IConfiguration configuration, ILogger<AssetsModel> logger)
    {
        _context = context;
        _configuration = configuration;
        _logger = logger;
        _assetsBasePath = configuration.GetValue<string>("AssetsBasePath")
            ?? Path.Combine(Directory.GetCurrentDirectory(), "Assets", "downloaded");
    }

    public List<GameAsset> Assets { get; set; } = new();
    public AssetStats Stats { get; set; } = new();
    public string? Message { get; set; }
    public bool IsError { get; set; }

    public async Task OnGetAsync()
    {
        Assets = await _context.GameAssets
            .OrderByDescending(a => a.UploadedAt)
            .ToListAsync();

        await CalculateStatsAsync();
    }

    public async Task<IActionResult> OnPostUploadAsync(
        IFormFile assetFile,
        string eaCdnPath,
        string category,
        string assetType,
        bool isRequired,
        string gameVersion,
        string? description)
    {
        try
        {
            if (assetFile == null || assetFile.Length == 0)
            {
                Message = "No file selected.";
                IsError = true;
                await OnGetAsync();
                return Page();
            }

            // Ensure assets directory exists
            if (!Directory.Exists(_assetsBasePath))
            {
                Directory.CreateDirectory(_assetsBasePath);
            }

            // Create category subdirectory
            var categoryPath = Path.Combine(_assetsBasePath, category);
            if (!Directory.Exists(categoryPath))
            {
                Directory.CreateDirectory(categoryPath);
            }

            // Save file to disk
            var fileName = Path.GetFileName(assetFile.FileName);
            var localPath = Path.Combine(categoryPath, fileName);

            using (var stream = new FileStream(localPath, FileMode.Create))
            {
                await assetFile.CopyToAsync(stream);
            }

            // Calculate MD5 and SHA256
            var md5Hash = await CalculateMd5Async(localPath);
            var sha256Hash = await CalculateSha256Async(localPath);
            var fileInfo = new FileInfo(localPath);

            // Normalize EA CDN path
            if (!eaCdnPath.StartsWith("/"))
            {
                eaCdnPath = "/" + eaCdnPath;
            }

            // Check if asset already exists
            var existingAsset = await _context.GameAssets
                .FirstOrDefaultAsync(a => a.EaCdnPath == eaCdnPath);

            if (existingAsset != null)
            {
                // Update existing asset
                existingAsset.FileName = fileName;
                existingAsset.LocalPath = localPath;
                existingAsset.FileSize = fileInfo.Length;
                existingAsset.Md5Hash = md5Hash;
                existingAsset.FileSha256 = sha256Hash;
                existingAsset.Category = category;
                existingAsset.AssetType = assetType;
                existingAsset.IsRequired = isRequired;
                existingAsset.Description = description;
                existingAsset.ContentType = GetContentType(fileName);
                existingAsset.Version = gameVersion;
                existingAsset.UploadedAt = DateTime.UtcNow;

                Message = $"Asset '{fileName}' updated successfully!";
            }
            else
            {
                // Create new asset
                var asset = new GameAsset
                {
                    FileName = fileName,
                    EaCdnPath = eaCdnPath,
                    LocalPath = localPath,
                    FileSize = fileInfo.Length,
                    Md5Hash = md5Hash,
                    FileSha256 = sha256Hash,
                    Category = category,
                    AssetType = assetType,
                    IsRequired = isRequired,
                    Description = description,
                    ContentType = GetContentType(fileName),
                    Version = gameVersion,
                    UploadedAt = DateTime.UtcNow,
                    DownloadedAt = DateTime.UtcNow
                };

                _context.GameAssets.Add(asset);
                Message = $"Asset '{fileName}' uploaded successfully!";
            }

            await _context.SaveChangesAsync();

            _logger.LogInformation("Asset uploaded: {FileName} -> {CdnPath}", fileName, eaCdnPath);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error uploading asset");
            Message = $"Error uploading asset: {ex.Message}";
            IsError = true;
        }

        await OnGetAsync();
        return Page();
    }

    public async Task<IActionResult> OnPostUploadZipAsync(
        IFormFile zipFile,
        string baseCategory,
        string? gameVersion,
        bool isRequired)
    {
        try
        {
            if (zipFile == null || zipFile.Length == 0)
            {
                Message = "No ZIP file selected.";
                IsError = true;
                await OnGetAsync();
                return Page();
            }

            if (!zipFile.FileName.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
            {
                Message = "Please upload a ZIP file.";
                IsError = true;
                await OnGetAsync();
                return Page();
            }

            // Ensure assets directory exists
            if (!Directory.Exists(_assetsBasePath))
            {
                Directory.CreateDirectory(_assetsBasePath);
            }

            // Save ZIP to temp location
            var tempZipPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".zip");
            using (var stream = new FileStream(tempZipPath, FileMode.Create))
            {
                await zipFile.CopyToAsync(stream);
            }

            int extractedCount = 0;
            int skippedCount = 0;
            var errors = new List<string>();
            
            // Try to parse manifest file if exists
            AssetManifest? manifest = null;
            using (var archive = ZipFile.OpenRead(tempZipPath))
            {
                var manifestEntry = archive.Entries.FirstOrDefault(e => 
                    e.Name.Equals("manifest.json", StringComparison.OrdinalIgnoreCase) ||
                    e.Name.Equals("manifest.xml", StringComparison.OrdinalIgnoreCase));
                
                if (manifestEntry != null)
                {
                    try
                    {
                        using var stream = manifestEntry.Open();
                        using var reader = new StreamReader(stream);
                        var manifestContent = await reader.ReadToEndAsync();
                        
                        if (manifestEntry.Name.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
                        {
                            manifest = JsonSerializer.Deserialize<AssetManifest>(manifestContent, 
                                new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
                        }
                        
                        if (manifest != null)
                        {
                            _logger.LogInformation("Loaded manifest: Version={Version}, GameVersion={GameVersion}", 
                                manifest.Version, manifest.GameVersion);
                            
                            // Override parameters from manifest if provided
                            if (!string.IsNullOrEmpty(manifest.GameVersion))
                                gameVersion = manifest.GameVersion;
                            if (!string.IsNullOrEmpty(manifest.Category) && baseCategory == "auto")
                                baseCategory = manifest.Category;
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.LogWarning(ex, "Failed to parse manifest file, using defaults");
                    }
                }
            }

            // Extract ZIP and process each file
            using (var archive = ZipFile.OpenRead(tempZipPath))
            {
                foreach (var entry in archive.Entries)
                {
                    try
                    {
                        // Skip directories and manifest files
                        if (string.IsNullOrEmpty(entry.Name) || entry.FullName.EndsWith("/") ||
                            entry.Name.Equals("manifest.json", StringComparison.OrdinalIgnoreCase) ||
                            entry.Name.Equals("manifest.xml", StringComparison.OrdinalIgnoreCase))
                            continue;

                        // Check if this file has manifest metadata
                        ManifestAsset? manifestAsset = manifest?.Assets?.FirstOrDefault(a => 
                            a.File.Replace("\\", "/").Equals(entry.FullName.Replace("\\", "/"), 
                            StringComparison.OrdinalIgnoreCase));

                        // Determine category from manifest or path in ZIP
                        var pathParts = entry.FullName.Split('/', '\\');
                        var category = manifestAsset?.Category ?? baseCategory;
                        
                        // Auto-detect if category is "auto"
                        if (category == "auto")
                        {
                            category = SmartDetectCategory(entry.FullName) ?? "base";
                        }
                        
                        // If ZIP has folders and no manifest, use first folder as subcategory
                        if (manifestAsset == null && pathParts.Length > 1 && category != "auto")
                        {
                            category = Path.Combine(category, pathParts[0]);
                        }

                        // Create category subdirectory
                        var categoryPath = Path.Combine(_assetsBasePath, category);
                        if (!Directory.Exists(categoryPath))
                        {
                            Directory.CreateDirectory(categoryPath);
                        }

                        // Extract file
                        var fileName = entry.Name;
                        var localPath = Path.Combine(categoryPath, fileName);

                        // Extract to disk
                        entry.ExtractToFile(localPath, overwrite: true);

                        // Calculate hashes
                        var md5Hash = await CalculateMd5Async(localPath);
                        var sha256Hash = await CalculateSha256Async(localPath);
                        var fileInfo = new FileInfo(localPath);

                        // Build EA CDN path from ZIP structure
                        var eaCdnPath = "/" + entry.FullName.Replace("\\", "/");
                        
                        // Determine asset type from manifest or filename
                        var assetType = manifestAsset?.Type ?? DetermineAssetType(fileName);
                        var required = manifestAsset?.Required ?? isRequired;
                        var description = manifestAsset?.Description;

                        // Check if asset already exists
                        var existingAsset = await _context.GameAssets
                            .FirstOrDefaultAsync(a => a.EaCdnPath == eaCdnPath);

                        if (existingAsset != null)
                        {
                            // Update existing
                            existingAsset.FileName = fileName;
                            existingAsset.LocalPath = localPath;
                            existingAsset.FileSize = fileInfo.Length;
                            existingAsset.Md5Hash = md5Hash;
                            existingAsset.FileSha256 = sha256Hash;
                            existingAsset.Category = category;
                            existingAsset.AssetType = assetType;
                            existingAsset.IsRequired = required;
                            existingAsset.Description = description;
                            existingAsset.ContentType = GetContentType(fileName);
                            existingAsset.Version = gameVersion;
                            existingAsset.UploadedAt = DateTime.UtcNow;
                            skippedCount++;
                        }
                        else
                        {
                            // Create new asset
                            var asset = new GameAsset
                            {
                                FileName = fileName,
                                EaCdnPath = eaCdnPath,
                                LocalPath = localPath,
                                FileSize = fileInfo.Length,
                                Md5Hash = md5Hash,
                                FileSha256 = sha256Hash,
                                Category = category,
                                AssetType = assetType,
                                IsRequired = required,
                                Description = description ?? $"Extracted from {zipFile.FileName}",
                                ContentType = GetContentType(fileName),
                                Version = gameVersion,
                                UploadedAt = DateTime.UtcNow,
                                DownloadedAt = DateTime.UtcNow
                            };

                            _context.GameAssets.Add(asset);
                            extractedCount++;
                        }
                    }
                    catch (Exception ex)
                    {
                        errors.Add($"{entry.FullName}: {ex.Message}");
                        _logger.LogError(ex, "Error extracting file from ZIP: {FileName}", entry.FullName);
                    }
                }
            }

            // Save changes
            await _context.SaveChangesAsync();

            // Clean up temp ZIP
            if (System.IO.File.Exists(tempZipPath))
            {
                System.IO.File.Delete(tempZipPath);
            }

            // Build message
            if (errors.Any())
            {
                Message = $"ZIP processed: {extractedCount} new, {skippedCount} updated. Errors: {errors.Count}";
                IsError = true;
            }
            else
            {
                Message = $"ZIP extracted successfully! {extractedCount} new files, {skippedCount} updated.";
            }

            _logger.LogInformation("ZIP uploaded: {FileName} -> {ExtractedCount} files", zipFile.FileName, extractedCount);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing ZIP file");
            Message = $"Error processing ZIP: {ex.Message}";
            IsError = true;
        }

        await OnGetAsync();
        return Page();
    }

    public async Task<IActionResult> OnPostDeleteAsync(int id)
    {
        try
        {
            var asset = await _context.GameAssets.FindAsync(id);
            if (asset == null)
            {
                Message = "Asset not found.";
                IsError = true;
                await OnGetAsync();
                return Page();
            }

            // Delete file from disk
            if (!string.IsNullOrEmpty(asset.LocalPath) && System.IO.File.Exists(asset.LocalPath))
            {
                System.IO.File.Delete(asset.LocalPath);
            }

            _context.GameAssets.Remove(asset);
            await _context.SaveChangesAsync();

            Message = $"Asset '{asset.FileName}' deleted successfully!";
            _logger.LogInformation("Asset deleted: {FileName}", asset.FileName);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error deleting asset");
            Message = $"Error deleting asset: {ex.Message}";
            IsError = true;
        }

        await OnGetAsync();
        return Page();
    }

    public async Task<IActionResult> OnPostGenerateManifestAsync()
    {
        try
        {
            var assets = await _context.GameAssets.ToListAsync();

            // Generate manifest in RR3 format (tab-separated)
            var manifestContent = new System.Text.StringBuilder();
            foreach (var asset in assets)
            {
                // Format: /path/to/file.ext    md5hash    compressedSize    uncompressedSize
                manifestContent.AppendLine($"{asset.EaCdnPath}\t{asset.Md5Hash}\t{asset.CompressedSize ?? asset.FileSize}\t{asset.FileSize}");
            }

            // Save to Assets directory
            var manifestPath = Path.Combine(_assetsBasePath, "asset_list_community.txt");
            await System.IO.File.WriteAllTextAsync(manifestPath, manifestContent.ToString());

            // Also generate JSON manifest for API
            var jsonManifest = assets.Select(a => new
            {
                path = a.EaCdnPath,
                md5 = a.Md5Hash,
                compressedSize = a.CompressedSize ?? a.FileSize,
                uncompressedSize = a.FileSize,
                category = a.Category,
                required = a.IsRequired
            });

            var jsonPath = Path.Combine(_assetsBasePath, "asset_manifest_community.json");
            await System.IO.File.WriteAllTextAsync(jsonPath, 
                System.Text.Json.JsonSerializer.Serialize(jsonManifest, new System.Text.Json.JsonSerializerOptions { WriteIndented = true }));

            Message = $"Manifest generated successfully! ({assets.Count} assets)";
            _logger.LogInformation("Asset manifest generated with {Count} assets", assets.Count);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error generating manifest");
            Message = $"Error generating manifest: {ex.Message}";
            IsError = true;
        }

        await OnGetAsync();
        return Page();
    }

    private async Task CalculateStatsAsync()
    {
        Stats.TotalAssets = Assets.Count;
        Stats.AvailableAssets = Assets.Count(a => !string.IsNullOrEmpty(a.LocalPath) && System.IO.File.Exists(a.LocalPath));
        Stats.TotalSizeMB = (long)(Assets.Sum(a => a.FileSize) / 1024.0 / 1024.0);
        Stats.TotalDownloads = Assets.Sum(a => a.AccessCount);
    }

    private async Task<string> CalculateMd5Async(string filePath)
    {
        using var md5 = MD5.Create();
        using var stream = System.IO.File.OpenRead(filePath);
        var hash = await md5.ComputeHashAsync(stream);
        return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
    }

    private async Task<string> CalculateSha256Async(string filePath)
    {
        using var sha256 = SHA256.Create();
        using var stream = System.IO.File.OpenRead(filePath);
        var hash = await sha256.ComputeHashAsync(stream);
        return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
    }

    private string GetContentType(string fileName)
    {
        var extension = Path.GetExtension(fileName).ToLowerInvariant();
        return extension switch
        {
            ".pak" => "application/octet-stream",
            ".dat" => "application/octet-stream",
            ".nct" => "application/octet-stream",
            ".z" => "application/x-compress",
            ".json" => "application/json",
            ".xml" => "application/xml",
            ".png" => "image/png",
            ".jpg" or ".jpeg" => "image/jpeg",
            ".pvr" => "image/pvr",
            ".atlas" => "application/octet-stream",
            ".mp3" => "audio/mpeg",
            ".ogg" => "audio/ogg",
            ".wav" => "audio/wav",
            _ => "application/octet-stream"
        };
    }

    private string? SmartDetectCategory(string fullPath)
    {
        var lowerPath = fullPath.ToLowerInvariant();
        
        // Check for known keywords in path
        if (lowerPath.Contains("car") || lowerPath.Contains("vehicle") || lowerPath.Contains("automobile"))
            return "cars";
        if (lowerPath.Contains("track") || lowerPath.Contains("circuit") || lowerPath.Contains("course"))
            return "tracks";
        if (lowerPath.Contains("audio") || lowerPath.Contains("sound") || lowerPath.Contains("music"))
            return "audio";
        if (lowerPath.Contains("texture") || lowerPath.Contains("material") || lowerPath.Contains("skin"))
            return "textures";
        if (lowerPath.Contains("ui") || lowerPath.Contains("hud") || lowerPath.Contains("menu"))
            return "ui";
        if (lowerPath.Contains("event") || lowerPath.Contains("race") || lowerPath.Contains("challenge"))
            return "events";
        if (lowerPath.Contains("dlc") || lowerPath.Contains("expansion") || lowerPath.Contains("addon"))
            return "dlc";
        if (lowerPath.Contains("update") || lowerPath.Contains("patch"))
            return "updates";
        
        // Fall back to first folder name
        var pathParts = fullPath.Split('/', '\\');
        if (pathParts.Length > 1)
            return pathParts[0].ToLowerInvariant();
        
        return null; // Will default to "base"
    }

    private string DetermineAssetType(string fileName)
    {
        var extension = Path.GetExtension(fileName).ToLowerInvariant();
        return extension switch
        {
            ".png" or ".jpg" or ".jpeg" or ".pvr" or ".atlas" => "Texture",
            ".mp3" or ".ogg" or ".wav" => "Audio",
            ".json" or ".xml" => "Config",
            ".pak" or ".dat" or ".nct" => "Data",
            _ => "Data"
        };
    }
}

public class AssetStats
{
    public int TotalAssets { get; set; }
    public int AvailableAssets { get; set; }
    public long TotalSizeMB { get; set; }
    public int TotalDownloads { get; set; }
}

// Manifest models for automatic ZIP metadata detection
public class AssetManifest
{
    public string? Version { get; set; }
    public string? GameVersion { get; set; }
    public string? Description { get; set; }
    public string? Author { get; set; }
    public string? Category { get; set; }
    public List<ManifestAsset>? Assets { get; set; }
}

public class ManifestAsset
{
    public string File { get; set; } = string.Empty;
    public string? Category { get; set; }
    public string? Type { get; set; }
    public bool Required { get; set; } = true;
    public string? Description { get; set; }
}
