using Core.Enumerations;
using DomainResults.Common;
using ExtensionMethods;
using Extensions;

namespace FileSecurityService {

  /// <summary>
  /// 
  /// </summary>
  public interface IFileSecurityService {
    /// <summary>
    /// 
    /// </summary>
    /// <param name="fileName"></param>
    /// <param name="bytes"></param>
    /// <param name="contentType"></param>
    /// <returns></returns>
    (MediaTypes?, IDomainResult) CheckFileSignature(string fileName, byte[] bytes, string contentType);
  }

  public class FileSecurityService : IFileSecurityService {
    /// <summary>
    /// https://gist.github.com/qti3e/6341245314bf3513abb080677cd1c93b
    /// </summary>
    private readonly Dictionary<string, FileSignature> _data;

    /// <summary>
    /// 
    /// </summary>
    public FileSecurityService() {
      _data = new Dictionary<string, FileSignature>() {
        #region Documents
        {
          ".pdf",
          new FileSignature(new List<string> {
          "0,25504446"
        }, "application/pdf", MediaTypes.Document)
        },
        {
          ".rtf",
          new FileSignature(new List<string> {
          "0,7B5C72746631"
        }, "application/rtf", MediaTypes.Document)
        },
        {
          ".doc",
          new FileSignature(new List<string> {
          "0,0D444F43",
          "0,CF11E0A1B11AE100",
          "0,D0CF11E0A1B11AE1",
          "0,DBA52D00",
          "512,ECA5C100"
        }, "application/msword", MediaTypes.Document)
        },
        {
          ".xls",
          new FileSignature(new List<string> {
          "512,0908100000060500",
          "0,D0CF11E0A1B11AE1",
          "512,FDFFFFFF04",
          "512,FDFFFFFF20000000"
        }, "application/vnd.ms-excel", MediaTypes.Document)
        },
        {
          ".ppt",
          new FileSignature(new List<string> {
          "512,006E1EF0",
          "512,0F00E803",
          "512,A0461DF0",
          "0,D0CF11E0A1B11AE1",
          "512,FDFFFFFF04"
        }, "application/vnd.ms-powerpoint", MediaTypes.Document)
        },
        {
          ".docx",
          new FileSignature(new List<string> {
          "0,504B030414000600"
        }, "application/vnd.openxmlformats-officedocument.wordprocessingml.document", MediaTypes.Document)
        },
        {
          ".xlsx",
          new FileSignature(new List<string> {
          "0,504B030414000600"
        }, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", MediaTypes.Document)
        },
        {
          ".pptx",
          new FileSignature(new List<string> {
          "0,504B030414000600"
        }, "application/vnd.openxmlformats-officedocument.presentationml.presentation", MediaTypes.Document)
        },
        #endregion

        #region Images
        {
          ".jpg",
          new FileSignature(new List<string> {
          "0,FFD8"
        }, "image/jpeg", MediaTypes.Image)
        },
        {
          ".jpeg",
          new FileSignature(new List<string> {
          "0,FFD8"
        }, "image/jpeg", MediaTypes.Image)
        },
        {
          ".jpe",
          new FileSignature(new List<string> {
          "0,FFD8"
        }, "image/jpeg", MediaTypes.Image)
        },
        {
          ".jfif",
          new FileSignature(new List<string> {
          "0,FFD8"
        }, "image/jpeg", MediaTypes.Image)
        },

        {
          ".png",
          new FileSignature(new List<string> {
          "0,89504E470D0A1A0A"
        }, "image/png", MediaTypes.Image)
        },

        {
          ".webp",
          new FileSignature(new List<string> {
          "0,52494646"
        }, "image/webp", MediaTypes.Image)
        }
        #endregion
      };
    }

    /// <summary>
    /// Don't rely on or trust the FileName property without validation.
    /// </summary>
    /// <param name="fileName"></param>
    /// <param name="bytes"></param>
    /// <param name="contentType"></param>
    /// <returns></returns>
    public (MediaTypes?, IDomainResult) CheckFileSignature(string fileName, byte[] bytes, string contentType) {

      var data = _data.SingleOrDefault(x => x.Key == Path.GetExtension(fileName).ToLower()).Value;

      if (data == null)
        return IDomainResult.NotFound<MediaTypes?>();

      // check content type
      if (contentType != data.ContentType)
        return IDomainResult.Failed<MediaTypes?>();

      // check signatures
      foreach (var signature in data.Signatures) {
        var splitString = signature.Split(",");

        var offset = splitString[0].ToInt();
        var signBytes = Enumerable.Range(0, splitString[1].Length)
          .Where(x => x % 2 == 0)
          .Select(x => Convert.ToByte(splitString[1].Substring(x, 2), 16))
          .ToArray();

        var sample = new byte[signBytes.Length];
        Buffer.BlockCopy(bytes, offset, sample, 0, signBytes.Length);

        if (sample.XORCcomparison(signBytes)) return IDomainResult.Success(data.MediaType);
      }

      return IDomainResult.Failed<MediaTypes?>();
    }
  }
}