using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using RR3CommunityServer.Data;
using RR3CommunityServer.Models;
using System.Security.Cryptography;
using System.Text;

namespace RR3CommunityServer.Controllers;

[ApiController]
[Route("content/api")]
public class AssetsController : ControllerBase
{
    private readonly RR3DbContext _context;
    private readonly ILogger<AssetsController> _logger;
    private readonly IConfiguration _configuration;
    private readonly string _assetsBasePath;

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

    /// <summary>
    /// Get asset manifest - lists all available assets
    /// </summary>
    [HttpGet("manifest")]
    public async Task<ActionResult<SynergyResponse<List<AssetManifestEntry>>>> GetManifest(
        [FromQuery] string? category = null)
    {
        _logger.LogInformation("GetManifest request - category: {Category}", category ?? "all");

        var assets = await _context.GameAssets
            .Where(a => category == null || a.Category == category)
            .Select(a => new AssetManifestEntry
            {
                Path = a.EaCdnPath,
                Md5 = a.Md5Hash,
                CompressedSize = a.CompressedSize ?? a.FileSize,
                UncompressedSize = a.FileSize,
                Category = a.Category
            })
            .ToListAsync();

        var response = new SynergyResponse<List<AssetManifestEntry>>
        {
            resultCode = 0,
            message = "Success",
            data = assets
        };

        return Ok(response);
    }

    /// <summary>
    /// Download a specific asset file
    /// Matches Cloudcell CDN URL pattern: /path/to/asset.ext
    /// </summary>
    [HttpGet("{**assetPath}")]
    public async Task<IActionResult> DownloadAsset(string assetPath)
    {
        _logger.LogInformation("Asset download request: {Path}", assetPath);

        // Ensure path starts with /
        if (!assetPath.StartsWith("/"))
        {
            assetPath = "/" + assetPath;
        }

        // Find asset in database
        var asset = await _context.GameAssets
            .FirstOrDefaultAsync(a => a.EaCdnPath == assetPath || a.FileName == Path.GetFileName(assetPath));

        if (asset == null)
        {
            _logger.LogWarning("Asset not found: {Path}", assetPath);
            return NotFound(new { error = "Asset not found", path = assetPath });
        }

        // Get file path
        string? filePath = asset.LocalPath;
        if (string.IsNullOrEmpty(filePath) || !System.IO.File.Exists(filePath))
        {
            // Try to find file in assets directory
            filePath = FindAssetFile(assetPath);
            
            if (string.IsNullOrEmpty(filePath))
            {
                _logger.LogWarning("Asset file not found on disk: {Path}", assetPath);
                return NotFound(new { error = "Asset file not available", path = assetPath });
            }
        }

        // Verify MD5 if available
        if (!string.IsNullOrEmpty(asset.Md5Hash))
        {
            var fileMd5 = await CalculateMd5(filePath);
            if (!fileMd5.Equals(asset.Md5Hash, StringComparison.OrdinalIgnoreCase))
            {
                _logger.LogError("MD5 mismatch for {Path}: expected {Expected}, got {Actual}", 
                    assetPath, asset.Md5Hash, fileMd5);
                return StatusCode(500, new { error = "Asset integrity check failed" });
            }
        }

        // Update access tracking
        asset.AccessCount++;
        asset.LastAccessedAt = DateTime.UtcNow;
        await _context.SaveChangesAsync();

        // Determine content type
        var contentType = GetContentType(assetPath);

        // Stream file
        var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 8192, true);
        
        _logger.LogInformation("Serving asset: {Path} ({Size} bytes)", assetPath, asset.FileSize);

        return File(stream, contentType, enableRangeProcessing: true);
    }

    /// <summary>
    /// Get asset info (metadata)
    /// </summary>
    [HttpGet("info/{**assetPath}")]
    public async Task<ActionResult<SynergyResponse<object>>> GetAssetInfo(string assetPath)
    {
        if (!assetPath.StartsWith("/"))
        {
            assetPath = "/" + assetPath;
        }

        var asset = await _context.GameAssets
            .FirstOrDefaultAsync(a => a.EaCdnPath == assetPath);

        if (asset == null)
        {
            return NotFound(new { error = "Asset not found" });
        }

        var response = new SynergyResponse<object>
        {
            resultCode = 0,
            message = "Success",
            data = new
            {
                path = asset.EaCdnPath,
                fileName = asset.FileName,
                size = asset.FileSize,
                compressedSize = asset.CompressedSize,
                md5 = asset.Md5Hash,
                sha256 = asset.FileSha256,
                contentType = asset.ContentType,
                category = asset.Category,
                assetType = asset.AssetType,
                available = !string.IsNullOrEmpty(asset.LocalPath) && System.IO.File.Exists(asset.LocalPath),
                downloadedAt = asset.DownloadedAt,
                accessCount = asset.AccessCount,
                lastAccessed = asset.LastAccessedAt
            }
        };

        return Ok(response);
    }

    /// <summary>
    /// Check if assets are available for download
    /// </summary>
    [HttpGet("status")]
    public async Task<ActionResult<SynergyResponse<object>>> GetStatus()
    {
        var totalAssets = await _context.GameAssets.CountAsync();
        
        // Get all assets with LocalPath, then check file existence in memory
        var assetsWithPath = await _context.GameAssets
            .Where(a => !string.IsNullOrEmpty(a.LocalPath))
            .Select(a => a.LocalPath)
            .ToListAsync();
        
        var availableAssets = assetsWithPath.Count(path => System.IO.File.Exists(path));

        var categoryCounts = await _context.GameAssets
            .GroupBy(a => a.Category)
            .Select(g => new { Category = g.Key, Count = g.Count() })
            .ToListAsync();

        var response = new SynergyResponse<object>
        {
            resultCode = 0,
            message = "Success",
            data = new
            {
                totalAssets = totalAssets,
                availableAssets = availableAssets,
                percentageAvailable = totalAssets > 0 ? (availableAssets * 100.0 / totalAssets) : 0,
                categories = categoryCounts.Select(c => new
                {
                    name = c.Category ?? "uncategorized",
                    count = c.Count
                }),
                basePath = _assetsBasePath,
                status = availableAssets > 0 ? "ready" : "waiting_for_assets"
            }
        };

        return Ok(response);
    }

    #region Helper Methods

    private string? FindAssetFile(string assetPath)
    {
        // Try multiple possible locations
        var possiblePaths = new[]
        {
            Path.Combine(_assetsBasePath, assetPath.TrimStart('/')),
            Path.Combine(_assetsBasePath, Path.GetFileName(assetPath)),
            // Try in categorized folders
            Path.Combine(_assetsBasePath, "cars", Path.GetFileName(assetPath)),
            Path.Combine(_assetsBasePath, "tracks", Path.GetFileName(assetPath)),
            Path.Combine(_assetsBasePath, "audio", Path.GetFileName(assetPath)),
            Path.Combine(_assetsBasePath, "textures", Path.GetFileName(assetPath)),
            Path.Combine(_assetsBasePath, "ui", Path.GetFileName(assetPath)),
        };

        foreach (var path in possiblePaths)
        {
            if (System.IO.File.Exists(path))
            {
                return path;
            }
        }

        return null;
    }

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

    #endregion
}

/// <summary>
/// Asset manifest entry matching RR3 manifest format
/// </summary>
public class AssetManifestEntry
{
    public string Path { get; set; } = string.Empty;
    public string Md5 { get; set; } = string.Empty;
    public long CompressedSize { get; set; }
    public long UncompressedSize { get; set; }
    public string? Category { get; set; }
}
