using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;

#if UNITY_6000_0_OR_NEWER
namespace CrazyGames
{
    [System.Serializable]
    public class BuildOptimization
    {
        public string optimization;
        public string initialValue;
        public string buildValue;

        public BuildOptimization(string optimization, string initialValue, string buildValue)
        {
            this.optimization = optimization;
            this.initialValue = initialValue;
            this.buildValue = buildValue;
        }
    }

    [System.Serializable]
    public class BuildData
    {
        public string buildDateTime;
        public string buildScriptVersion;
        public string sdkVersion;
        public string unityVersion;
        public string buildVariant;
        public AdditionalBuildOptions additionalOptions;
        public bool usesProgressiveAssetLoading;
        public string buildResult;
        public double totalTime;
        public List<BuildOptimization> buildOptimizations;
        public List<GeneratedFile> generatedFiles;
    }

    [System.Serializable]
    public class GeneratedFile
    {
        public string filename;
        public string type;
        public ulong uncompressedSize;
        public ulong size;
    }

    /// <summary>
    /// Mirrors the files.json generated by the CrazyGames WebGL template in the build output folder.
    /// Paths are relative to the build output folder, ex Build/cdadfac5d5a5d2c11639cfca63e6d4f8.data.br
    /// </summary>
    [System.Serializable]
    public class BuildFiles
    {
        public const string FILE_NAME = "files.json";

        /// <summary>
        /// Single data file, present only when progressive asset loading is disabled.
        /// </summary>
        public string dataFile;

        /// <summary>
        /// Data files needed to start the game, present only when progressive asset loading is enabled.
        /// They are placed directly in the build output folder, ex level0.br, Resources/unity default resources.br
        /// </summary>
        public string[] primaryDataFiles;

        /// <summary>
        /// Data files streamed after the game starts, present only when progressive asset loading is enabled.
        /// </summary>
        public string[] secondaryDataFiles;

        public string codeFile;
        public string loaderFile;
        public string frameworkFile;

        public bool IsProgressive => primaryDataFiles != null && primaryDataFiles.Length > 0;

        public static bool Exists(string buildOutputPath)
        {
            return File.Exists(Path.Combine(buildOutputPath, FILE_NAME));
        }

        public static BuildFiles Read(string buildOutputPath)
        {
            var path = Path.Combine(buildOutputPath, FILE_NAME);
            if (!File.Exists(path))
            {
                throw new FileNotFoundException(
                    $"{FILE_NAME} not found in the build output, make sure the CrazyGames WebGL template is used.",
                    path
                );
            }
            return JsonUtility.FromJson<BuildFiles>(File.ReadAllText(path));
        }
    }

    /// <summary>
    /// Generates the report that will be uploaded to Developer Portal, contains information about the build.
    /// </summary>
    public class BuildReportGenerator
    {
        private const string BUILD_SCRIPT_VERSION = "1.6.0";

        public void GenerateReport(
            BuildReport report,
            BuildVariant variant,
            AdditionalBuildOptions additionalOptions,
            string buildPath,
            DateTime startTime,
            List<BuildOptimization> buildOptimizations,
            List<IntermediateFile> intermediateFiles
        )
        {
            var generatedFiles = GetMainFiles(buildPath);
            generatedFiles.AddRange(intermediateFiles.Select(f => CreateGeneratedFile(buildPath, f.path, f.type)));

            var buildData = new BuildData
            {
                buildDateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                buildScriptVersion = BUILD_SCRIPT_VERSION,
                sdkVersion = CrazySDK.Version,
                unityVersion = Application.unityVersion,
                buildVariant = variant.ToString(),
                additionalOptions = additionalOptions,
                usesProgressiveAssetLoading = BuildFiles.Read(buildPath).IsProgressive,
                buildResult = report.summary.result.ToString(),
                totalTime = (DateTime.Now - startTime).TotalSeconds,
                buildOptimizations = buildOptimizations,
                generatedFiles = generatedFiles,
            };

            var json = JsonUtility.ToJson(buildData, true);
            var reportPath = Path.Combine(buildPath, "crazygames_build_report.json");

            File.WriteAllText(reportPath, json);
        }

        /// <summary>
        /// Type of the data files streamed after the game starts, these don't count towards the initial load size.
        /// </summary>
        public const string SECONDARY_DATA_TYPE = "data_secondary";

        /// <summary>
        /// Returns the files of the main build, as listed in the files.json generated by the CrazyGames WebGL template.
        /// </summary>
        public static List<GeneratedFile> GetMainFiles(string buildOutputPath)
        {
            var buildFiles = BuildFiles.Read(buildOutputPath);
            var files = new List<GeneratedFile>
            {
                CreateGeneratedFile(buildOutputPath, buildFiles.frameworkFile, "framework"),
                CreateGeneratedFile(buildOutputPath, buildFiles.loaderFile, "loader"),
                CreateGeneratedFile(buildOutputPath, buildFiles.codeFile, "wasm"),
            };

            if (buildFiles.IsProgressive)
            {
                files.AddRange(buildFiles.primaryDataFiles.Select(f => CreateGeneratedFile(buildOutputPath, f, "data_primary")));
                files.AddRange(buildFiles.secondaryDataFiles.Select(f => CreateGeneratedFile(buildOutputPath, f, SECONDARY_DATA_TYPE)));
            }
            else
            {
                files.Add(CreateGeneratedFile(buildOutputPath, buildFiles.dataFile, "data"));
            }

            // OrderBy is stable, so the data files keep their loading order
            return files.OrderBy(f => f.type).ToList();
        }

        /// <param name="relativePath">Path relative to the build output folder, ex Build/cdadfac5d5a5d2c11639cfca63e6d4f8.data.br or Resources/unity default resources.br</param>
        private static GeneratedFile CreateGeneratedFile(string buildOutputPath, string relativePath, string type)
        {
            var filePath = Path.Combine(buildOutputPath, relativePath);
            relativePath = relativePath.Replace('\\', '/');
            // files from the Build folder are identified by their name, progressive data files are identified by their path,
            // as the Unity loader uses the path to place them in the virtual file system
            var filename = relativePath.StartsWith("Build/") ? Path.GetFileName(relativePath) : relativePath;
            return new GeneratedFile
            {
                filename = filename,
                type = type,
                uncompressedSize = GetUncompressedSize(filePath, filename),
                size = (ulong)new FileInfo(filePath).Length,
            };
        }

        private static ulong GetUncompressedSize(string filePath, string filename)
        {
            if (filename.EndsWith(".br", StringComparison.OrdinalIgnoreCase))
            {
                try
                {
                    using (var compressedFile = File.OpenRead(filePath))
                    using (var brotli = new BrotliStream(compressedFile, CompressionMode.Decompress))
                    using (var memoryStream = new MemoryStream())
                    {
                        brotli.CopyTo(memoryStream);
                        return (ulong)memoryStream.Length;
                    }
                }
                catch (Exception e)
                {
                    Debug.LogWarning($"Could not get uncompressed size for {filename}: {e.Message}");
                    return (ulong)new FileInfo(filePath).Length;
                }
            }

            return (ulong)new FileInfo(filePath).Length;
        }
    }
}
#endif
