using Dinah.Core;
using Dinah.Core.Diagnostics;
using Dinah.Core.IO;
using Dinah.Core.StepRunner;
using System;
using System.IO;

namespace AaxDecrypter
{
    public interface ISimpleAaxcToM4bConverter
    {
        event EventHandler<AaxcTagLibFile> RetrievedTags;
        event EventHandler<byte[]> RetrievedCoverArt;
        event EventHandler<TimeSpan> DecryptTimeRemaining;
        event EventHandler<int> DecryptProgressUpdate;
        bool Run();
        string AppName { get; set; }
        string outDir { get; }
        string outputFileName { get; }
        DownloadLicense downloadLicense { get; }
        AaxcTagLibFile aaxcTagLib { get; }
        byte[] coverArt { get; }
        void SetCoverArt(byte[] coverArt);
        void SetOutputFilename(string outFileName);
    }
    public interface IAdvancedAaxcToM4bConverter : ISimpleAaxcToM4bConverter
    {
        void Cancel();
        bool Step1_CreateDir();
        bool Step2_GetMetadata();
        bool Step3_DownloadAndCombine();
        bool Step4_RestoreMetadata();
        bool Step5_CreateCue();
        bool Step6_CreateNfo();
        bool Step7_Cleanup();
    }
    public class AaxcDownloadConverter : IAdvancedAaxcToM4bConverter
    {
        public event EventHandler<AaxcTagLibFile> RetrievedTags;
        public event EventHandler<byte[]> RetrievedCoverArt;
        public event EventHandler<int> DecryptProgressUpdate;
        public event EventHandler<TimeSpan> DecryptTimeRemaining;
        public string AppName { get; set; } = nameof(AaxcDownloadConverter);
        public string outDir { get; private set; }
        public string cacheDir { get; private set; }
        public string outputFileName { get; private set; }
        public DownloadLicense downloadLicense { get; private set; }
        public AaxcTagLibFile aaxcTagLib { get; private set; }
        public byte[] coverArt { get; private set; }

        private StepSequence steps { get; }
        private FFMpegAaxcProcesser aaxcProcesser;
        private bool isCanceled { get; set; }
        private string jsonDownloadState => Path.Combine(cacheDir, Path.GetFileNameWithoutExtension(outputFileName) + ".json");
        private string tempFile => PathLib.ReplaceExtension(jsonDownloadState, ".aaxc");

        public static AaxcDownloadConverter Create(string cacheDirectory, string outDirectory, DownloadLicense dlLic)
        {
            var converter = new AaxcDownloadConverter(cacheDirectory, outDirectory, dlLic);
            converter.SetOutputFilename(Path.GetTempFileName());
            return converter;
        }

        private AaxcDownloadConverter(string cacheDirectory, string outDirectory, DownloadLicense dlLic)
        {
            ArgumentValidator.EnsureNotNullOrWhiteSpace(outDirectory, nameof(outDirectory));
            ArgumentValidator.EnsureNotNull(dlLic, nameof(dlLic));

            if (!Directory.Exists(outDirectory))
                throw new ArgumentNullException(nameof(cacheDirectory), "Directory does not exist");
            if (!Directory.Exists(outDirectory))
                throw new ArgumentNullException(nameof(outDirectory), "Directory does not exist");

            cacheDir = cacheDirectory;
            outDir = outDirectory;

            steps = new StepSequence
            {
                Name = "Download and Convert Aaxc To M4b",

                ["Step 1: Create Dir"] = Step1_CreateDir,
                ["Step 2: Get Aaxc Metadata"] = Step2_GetMetadata,
                ["Step 3: Download Decrypted Audiobook"] = Step3_DownloadAndCombine,
                ["Step 4: Restore Aaxc Metadata"] = Step4_RestoreMetadata,
                ["Step 5: Create Cue"] = Step5_CreateCue,
                ["Step 6: Create Nfo"] = Step6_CreateNfo,
                ["Step 7: Cleanup"] = Step7_Cleanup,
            };

            aaxcProcesser = new FFMpegAaxcProcesser(dlLic);
            aaxcProcesser.ProgressUpdate += AaxcProcesser_ProgressUpdate;

            downloadLicense = dlLic;
        }

        public void SetOutputFilename(string outFileName)
        {
            outputFileName = PathLib.ReplaceExtension(outFileName, ".m4b");
            outDir = Path.GetDirectoryName(outputFileName);

            if (File.Exists(outputFileName))
                File.Delete(outputFileName);
        }

        public void SetCoverArt(byte[] coverArt)
        {
            if (coverArt is null) return;

            this.coverArt = coverArt;
            RetrievedCoverArt?.Invoke(this, coverArt);
        }

        public bool Run()
        {
            var (IsSuccess, Elapsed) = steps.Run();

            if (!IsSuccess)
            {
                Console.WriteLine("WARNING-Conversion failed");
                return false;
            }

            var speedup = (int)(aaxcTagLib.Properties.Duration.TotalSeconds / (long)Elapsed.TotalSeconds);
            Console.WriteLine("Speedup is " + speedup + "x realtime.");
            Console.WriteLine("Done");
            return true;
        }

        public bool Step1_CreateDir()
        {
            ProcessRunner.WorkingDir = outDir;
            Directory.CreateDirectory(outDir);

            return !isCanceled;
        }

        public bool Step2_GetMetadata()
        {
            //Get metadata from the file over http

            NetworkFileStreamPersister nfsPersister;
            if (File.Exists(jsonDownloadState))
            {
                nfsPersister = new NetworkFileStreamPersister(jsonDownloadState);
                //If More thaan ~1 hour has elapsed since getting the download url, it will expire.
                //The new url will be to the same file.
                nfsPersister.NetworkFileStream.SetUriForSameFile(new Uri(downloadLicense.DownloadUrl));
            }
            else
            {
                var headers = new System.Net.WebHeaderCollection();
                headers.Add("User-Agent", downloadLicense.UserAgent);

                NetworkFileStream networkFileStream = new NetworkFileStream(tempFile, new Uri(downloadLicense.DownloadUrl), 0, headers);
                nfsPersister = new NetworkFileStreamPersister(networkFileStream, jsonDownloadState);
            }

            var networkFile = new NetworkFileAbstraction(nfsPersister.NetworkFileStream);
            aaxcTagLib = new AaxcTagLibFile(networkFile);
            nfsPersister.Dispose();


            if (coverArt is null && aaxcTagLib.AppleTags.Pictures.Length > 0)
            {
                coverArt = aaxcTagLib.AppleTags.Pictures[0].Data.Data;
            }

            RetrievedTags?.Invoke(this, aaxcTagLib);
            RetrievedCoverArt?.Invoke(this, coverArt);

            return !isCanceled;
        }

        public bool Step3_DownloadAndCombine()
        {
            DecryptProgressUpdate?.Invoke(this, int.MaxValue);

            NetworkFileStreamPersister nfsPersister;
            if (File.Exists(jsonDownloadState))
            {
                nfsPersister = new NetworkFileStreamPersister(jsonDownloadState);
                //If More thaan ~1 hour has elapsed since getting the download url, it will expire.
                //The new url will be to the same file.
                nfsPersister.NetworkFileStream.SetUriForSameFile(new Uri(downloadLicense.DownloadUrl));
            }
            else
            {
                var headers = new System.Net.WebHeaderCollection();
                headers.Add("User-Agent", downloadLicense.UserAgent);

                NetworkFileStream networkFileStream = new NetworkFileStream(tempFile, new Uri(downloadLicense.DownloadUrl), 0, headers);
                nfsPersister = new NetworkFileStreamPersister(networkFileStream, jsonDownloadState);
            }

            string metadataPath = Path.Combine(outDir, Path.GetFileName(outputFileName) + ".ffmeta");

            if (downloadLicense.ChapterInfo is null)
            {
                //If we want to keep the original chapters, we need to get them from the url.
                //Ffprobe needs to seek to find metadata and it can't seek a pipe. Also, there's
                //no guarantee that enough of the file will have been downloaded at this point
                //to be able to use the cache file.
                downloadLicense.ChapterInfo = new ChapterInfo(downloadLicense.DownloadUrl);
            }

            //Only write chapters to the metadata file. All other aaxc metadata will be
            //wiped out but is restored in Step 3.                
            File.WriteAllText(metadataPath, downloadLicense.ChapterInfo.ToFFMeta(true));


            aaxcProcesser.ProcessBook(
                nfsPersister.NetworkFileStream,
                outputFileName,
                metadataPath)
                .GetAwaiter()
                .GetResult();

            nfsPersister.NetworkFileStream.Close();
            nfsPersister.Dispose();

            FileExt.SafeDelete(metadataPath);

            DecryptProgressUpdate?.Invoke(this, 0);

            return aaxcProcesser.Succeeded && !isCanceled;
        }

        private void AaxcProcesser_ProgressUpdate(object sender, AaxcProcessUpdate e)
        {
            double remainingSecsToProcess = (aaxcTagLib.Properties.Duration - e.ProcessPosition).TotalSeconds;
            double estTimeRemaining = remainingSecsToProcess / e.ProcessSpeed;

            if (double.IsNormal(estTimeRemaining))
                DecryptTimeRemaining?.Invoke(this, TimeSpan.FromSeconds(estTimeRemaining));

            double progressPercent = 100 * e.ProcessPosition.TotalSeconds / aaxcTagLib.Properties.Duration.TotalSeconds;

            DecryptProgressUpdate?.Invoke(this, (int)progressPercent);
        }

        /// <summary>
        /// Copy all aacx metadata to m4b file, including cover art.
        /// </summary>
        public bool Step4_RestoreMetadata()
        {
            var outFile = new AaxcTagLibFile(outputFileName);
            outFile.CopyTagsFrom(aaxcTagLib);

            if (outFile.AppleTags.Pictures.Length == 0 && coverArt is not null)
            {
                outFile.AddPicture(coverArt);
            }

            outFile.Save();

            return !isCanceled;
        }

        public bool Step5_CreateCue()
        {
            try
            {
                File.WriteAllText(PathLib.ReplaceExtension(outputFileName, ".cue"), Cue.CreateContents(Path.GetFileName(outputFileName), downloadLicense.ChapterInfo));
            }
            catch (Exception ex)
            {
                Serilog.Log.Logger.Error(ex, $"{nameof(Step5_CreateCue)}. FAILED");
            }
            return !isCanceled;
        }

        public bool Step6_CreateNfo()
        {
            try
            {
                File.WriteAllText(PathLib.ReplaceExtension(outputFileName, ".nfo"), NFO.CreateContents(AppName, aaxcTagLib, downloadLicense.ChapterInfo));
            }
            catch (Exception ex)
            {
                Serilog.Log.Logger.Error(ex, $"{nameof(Step6_CreateNfo)}. FAILED");
            }
            return !isCanceled;
        }

        public bool Step7_Cleanup()
        {
            FileExt.SafeDelete(jsonDownloadState);
            FileExt.SafeDelete(tempFile);
            return !isCanceled;
        }

        public void Cancel()
        {
            isCanceled = true;
            aaxcProcesser.Cancel();
        }
    }
}
