using System;
using System.Globalization;
using System.IO;
using System.Text;
using ZeroLevel.Services.Encryption;

namespace ZeroLevel.Logging
{
    public class EncryptedFileLogOptions
    {
        public EncryptedFileLogOptions()
        {
            Folder = null!;
            LimitFileSize = 0;
        }

        internal string Key { get; private set; }
        internal string Folder { get; private set; }
        internal long LimitFileSize { get; private set; }

        public EncryptedFileLogOptions SetKey(string key)
        {
            this.Key = key;
            return this;
        }

        public EncryptedFileLogOptions SetFolderPath(string folder)
        {
            if (folder.IndexOf(':') < 0)
            {
                this.Folder = Path.Combine(Configuration.BaseDirectory, folder);
            }
            else
            {
                this.Folder = folder;
            }
            return this;
        }

        public EncryptedFileLogOptions SetMaximumFileSizeInKb(long size)
        {
            this.LimitFileSize = size;
            return this;
        }
    }

    public class EncryptedFileLog
        : ILogger
    {
        #region Fields

        private readonly EncryptedFileLogOptions _options;
        private int _todayCountLogFiles = 0;

        /// <summary>
        /// Current log file
        /// </summary>
        private string _currentLogFile;

        /// <summary>
        /// Stream to output to file
        /// </summary>
        private Stream _writer;

        /// <summary>
        /// Lock on re-create file
        /// </summary>
        private readonly object _fileRecreating = new object();

        private long _currentLogSize = 0;

        private readonly long _taskRename = -1;

        private readonly FastObfuscator _obfuscator;

        #endregion Fields

        public EncryptedFileLog(EncryptedFileLogOptions options)
        {
            if (options == null!)
                throw new ArgumentNullException(nameof(options));
            if (string.IsNullOrEmpty(options.Key))
                throw new ArgumentNullException("options.Key");
            _options = options;
            if (string.IsNullOrWhiteSpace(_options.Folder))
            {
                _options.SetFolderPath(Path.Combine(Configuration.BaseDirectory, "logs"));
            }
            if (!Directory.Exists(_options.Folder))
            {
                var dir = Directory.CreateDirectory(_options.Folder);
                if (dir.Exists == false)
                {
                    throw new ArgumentException($"Can't create or found directory '{_options.Folder}'");
                }
            }
            _obfuscator = new FastObfuscator(options.Key);
            RecreateLogFile();
            _taskRename = Sheduller.RemindEveryNonlinearPeriod(() =>
                (DateTime.Now.AddDays(1).Date - DateTime.Now).Add(TimeSpan.FromMilliseconds(100)),
                RecreateLogFile);
        }

        #region Utils

        /// <summary>
        /// Checking the name of the log file (changes when the date changes to the next day)
        /// </summary>
        private void RecreateLogFile()
        {
            lock (_fileRecreating)
            {
                var nextFileName = GetNextFileName();
                CloseCurrentWriter();
                Stream stream = null!;
                try
                {
                    _currentLogFile = nextFileName;
                    _writer = new FileStream(_currentLogFile, FileMode.Append, FileAccess.Write, FileShare.Read);
                }
                catch
                {
                    if (stream != null!)
                    {
                        stream.Dispose();
                    }
                    throw;
                }
            }
        }

        /// <summary>
        /// Closing the current log
        /// </summary>
        private void CloseCurrentWriter()
        {
            if (_writer != null!)
            {
                _writer.Dispose();
                _writer = null!;
            }
        }

        private string GetNextFileName()
        {
            string fileName = Path.Combine(_options.Folder, string.Format(CultureInfo.CurrentCulture, "{0:yyyyMMdd}_{1:D4}.bin", DateTime.Now, _todayCountLogFiles));
            if (_options.LimitFileSize > 0)
            {
                if (!File.Exists(fileName))
                {
                    _todayCountLogFiles = 0;
                }
                else
                {
                    while (File.Exists(fileName))
                    {
                        var length = (new FileInfo(fileName).Length >> 10);
                        if (length >= _options.LimitFileSize)
                        {
                            _todayCountLogFiles++;
                            fileName = Path.Combine(_options.Folder, string.Format(CultureInfo.CurrentCulture, "{0:yyyyMMdd}_{1:D4}.bin", DateTime.Now, _todayCountLogFiles));
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }
            return fileName;
        }

        #endregion Utils

        #region IDisposable

        private bool _disposed = false;

        public void Dispose()
        {
            Sheduller.Remove(_taskRename);
            if (false == _disposed)
            {
                _disposed = true;
                CloseCurrentWriter();
            }
        }

        #endregion IDisposable

        #region ILog

        public void Write(LogLevel level, string message)
        {
            if (false == _disposed)
            {
                byte[] data;
                if (level == LogLevel.Raw)
                {
                    data = Encoding.UTF8.GetBytes(message);
                }
                else
                {
                    data = Encoding.UTF8.GetBytes($"[{DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss")} {LogLevelNameMapping.CompactName(level)}]\t{message}\r\n");
                }
                _obfuscator.HashData(data);
                lock (_fileRecreating)
                {
                    _writer.Write(BitConverter.GetBytes(data.Length), 0, 4);
                    _writer.Write(data, 0, data.Length);
                }
                _currentLogSize += data.Length;
                if (_options.LimitFileSize > 0 && _currentLogSize >> 10 >= _options.LimitFileSize)
                {
                    RecreateLogFile();
                }
            }
        }

        #endregion ILog
    }
}