using log4net;
using Org.BouncyCastle.Tsp;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Windows.Forms;
using System.Windows.Input;
using TSManagementApp.Communication;
using TSManagementApp.Models;
using TSManagementApp.Properties;
using TSManagementApp.Utils;
using TSManagementApp.Views;

namespace TSManagementApp.ViewModels
{
    public class VerifyTimeStampViewModel : BaseViewModel
    {
        #region Logger
        private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        #endregion

        private VerifyTimeStampView _viewVerifyTs;
        public VerifyTimeStampViewModel(VerifyTimeStampView view)
        {
            _viewVerifyTs = view;
            InitCommands();
        }

        public VerifyTimeStampViewModel(VerifyTimeStampView view, string timeStampFilePath)
        {
            _viewVerifyTs = view;
            InitCommands();
            TimeStampFilePath = timeStampFilePath;
            SourceFilePath = FindSourceFile();
            AddToListIsChecked = true;
        }

        #region Properties

        private string _timeStampFilePath;
        public string TimeStampFilePath { get => _timeStampFilePath; set { _timeStampFilePath = value; OnPropertyChanged("TimeStampFilePath"); } }
        private string _sourceFilePath;
        public string SourceFilePath { get => _sourceFilePath; set { _sourceFilePath = value; OnPropertyChanged("SourceFilePath"); } }
        private bool _addToListIsChecked;
        public bool AddToListIsChecked { get => _addToListIsChecked; set { _addToListIsChecked = value; OnPropertyChanged("AddToListIsChecked"); } }


        #endregion

        #region Commands, Handlers

        public ICommand CommandChooseTimeStampFile { get; set; }
        public ICommand CommandChooseSourceFile { get; set; }
        public ICommand CommandVerify { get; set; }

        public void ChooseTimeStampFile()
        {
            try
            {
                var str = GetFilePathFromOpenFileDialog(Resources.VerifyTsVM_ChooseTsFile, $"{Resources.TsDetailsVM_TsrFile}|*.tsr;*.tst|{Resources.CreateTsVM_Allfiles}|*.*");
                if (!string.IsNullOrWhiteSpace(str))
                    TimeStampFilePath = str;
                if (!string.IsNullOrWhiteSpace(TimeStampFilePath))
                    SourceFilePath = FindSourceFile();
            }
            catch (Exception ex)
            {
                Messages.MsgError($"{Resources.VerifyTsVM_ErrorWhileSettingTsFile} - {ex.Message}");
                log.Error($"Error while opening time stamp file - {ex.ToString()}");
            }
        }

        public void ChooseSourceFile()
        {
            try
            {
                var str = GetFilePathFromOpenFileDialog(Resources.VerifyTsVM_ChooseSourceFile,
                    $"{Resources.CreateTsVM_PdfFile}|*.pdf|{Resources.CreateTsVM_Textfiles}|*.txt;*.docx;*.doc;*.rtf|{Resources.CreateTsVM_Allfiles}|*.*");
                if (!string.IsNullOrWhiteSpace(str))
                    SourceFilePath = str;
            }
            catch (Exception ex)
            {
                Messages.MsgError($"{Resources.VerifyTsVM_ErrorWhileSettingSourceFile} - {ex.Message}");
                log.Error($"Error while opening source file - {ex.ToString()}");
            }
        }

        public void Verify()
        {
            try
            {
                string errMessage = ValidateInputValues();
                if (!string.IsNullOrWhiteSpace(errMessage))
                {
                    Messages.MsgError($"{Resources.VerifyTsVM_VerifyError}:\n{errMessage}");
                    return;
                }

                FileInfo tsFileInfo = new FileInfo(TimeStampFilePath);
                if (!string.IsNullOrWhiteSpace(TimeStampFilePath) && File.Exists(TimeStampFilePath))
                {
                    log.Info($"Start to verify time stamp. TimeStamp - {tsFileInfo.FullName}, source file - {SourceFilePath}");
                    byte[] tsData = File.ReadAllBytes(TimeStampFilePath);
                    if (IsTimeStampToken(tsData) || IsTimeStampResponse(tsData))
                    {
                        if (IsTimeStampResponse(tsData))
                        {
                            TimeStampResponse timeStampResponse = new TimeStampResponse(File.ReadAllBytes(TimeStampFilePath));
                            string verifyErr = VerifyTSResponse(timeStampResponse);
                            if (string.IsNullOrWhiteSpace(verifyErr))
                            {
                                Messages.MsgInfo(Resources.VerifyTsVM_MessageVerifyOk);
                                _viewVerifyTs.Close();
                                if (AddToListIsChecked)
                                    AddTimeStampToList(timeStampResponse, tsFileInfo);
                            }
                            else
                            {
                                Messages.MsgError($"{Resources.VerifyTsVM_VerifyEndsWithError}:\n{verifyErr}");
                                log.Info($"Verification of time stamp {tsFileInfo.Name} ended with error. Error details - {verifyErr}");
                                if (AddToListIsChecked)
                                    AddTimeStampToList(timeStampResponse, tsFileInfo);
                                _viewVerifyTs.Close();
                            }
                        }
                        else if (IsTimeStampToken(tsData))
                        {
                            TimeStampToken timeStampToken = new TimeStampToken(new Org.BouncyCastle.Cms.CmsSignedData(File.ReadAllBytes(TimeStampFilePath)));
                            string verifyErr = VerifyTSToken(timeStampToken);
                            if (string.IsNullOrWhiteSpace(verifyErr))
                            {
                                Messages.MsgInfo(Resources.VerifyTsVM_MessageVerifyOk);
                                _viewVerifyTs.Close();
                                if (AddToListIsChecked)
                                    AddTimeStampToList(timeStampToken, tsFileInfo);
                            }
                            else
                            {
                                Messages.MsgError($"{Resources.VerifyTsVM_VerifyEndsWithError}:\n{verifyErr}");
                                log.Info($"Verification of time stamp {tsFileInfo.Name} ended with error. Error details - {verifyErr}");
                                if (AddToListIsChecked)
                                    AddTimeStampToList(timeStampToken, tsFileInfo);
                                _viewVerifyTs.Close();
                            }
                        }
                        else
                            throw new Exception(Resources.VerifyTsVM_NotTsFile);
                    }
                    else
                        throw new Exception(Resources.VerifyTsVM_NotTsFile2);
                }
                else
                    throw new Exception(Resources.VerifyTsVM_FileNotExists);
            }
            catch (Exception ex)
            {
                log.Error($"Error while verifying time stamp - {ex.ToString()}");
                Messages.MsgError($"{Resources.VerifyTsVM_MessageErrorVerifyTs} - {ex.Message}" );
            }
        }

        #endregion

        #region PrivateMethods

        private void InitCommands()
        {
            CommandChooseTimeStampFile = new CommandGeneric(ChooseTimeStampFile);
            CommandChooseSourceFile = new CommandGeneric(ChooseSourceFile);
            CommandVerify = new CommandGeneric(Verify);
        }

        private string FindSourceFile()
        {
            FileInfo tsFileInfo = new FileInfo(TimeStampFilePath);
            string[] files = Directory.GetFiles(tsFileInfo.DirectoryName);
            foreach (string file in files)
            {
                FileInfo info = new FileInfo(file);
                if (info.Name == tsFileInfo.Name.Replace(tsFileInfo.Extension, null) && info.Extension != ".tsr")
                    return info.FullName;
            }
            return null;
        }

        private string GetFilePathFromOpenFileDialog(string title, string filter)
        {
            try
            {
                using (OpenFileDialog dialog = new OpenFileDialog())
                {
                    dialog.Title = title;
                    dialog.Filter = filter;
                    dialog.Multiselect = false;
                    if (dialog.ShowDialog() == DialogResult.OK)
                        return dialog.FileName;
                    else
                        return null;
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private string VerifyTSResponse(TimeStampResponse tsResponse)
        {
            StringBuilder errMsg = new StringBuilder();
            if (tsResponse.Status != 0 && tsResponse.Status != 1)
                errMsg.AppendLine($"{tsResponse.Status} - {tsResponse.GetStatusString()}");
            try { BouncyCastleTSProvider.ValidateSignature(tsResponse); }
            catch (Exception e) { errMsg.AppendLine(Resources.VerifyTsVM_InvalidSignature); }
            if (tsResponse.TimeStampToken != null)
                errMsg.AppendLine(VerifyTSToken(tsResponse.TimeStampToken));
            
            return errMsg.ToString();
        }

        private string VerifyTSToken(TimeStampToken tsToken)
        {
            StringBuilder errMsg = new StringBuilder();
            string hashOid = tsToken.TimeStampInfo.MessageImprintAlgOid;
            HashAlgorithm sourceFileImprint = null;
            FileStream sourceFS = new FileStream(SourceFilePath, FileMode.Open);
            switch (hashOid)
            {
                case "1.3.14.3.2.26":
                    sourceFileImprint = SHA1.Create();
                    sourceFileImprint.ComputeHash(sourceFS);
                    break;
                case "2.16.840.1.101.3.4.2.1":
                    sourceFileImprint = SHA256.Create();
                    sourceFileImprint.ComputeHash(sourceFS);
                    break;
                case "2.16.840.1.101.3.4.2.3":
                    sourceFileImprint = SHA512.Create();
                    sourceFileImprint.ComputeHash(sourceFS);
                    break;
                default:
                    errMsg.AppendLine(Resources.TsDetailsVM_UnsupportedHash); break;
            }

            sourceFS.Dispose();

            if (sourceFileImprint.Hash.Length != tsToken.TimeStampInfo.GetMessageImprintDigest().Length)
                errMsg.AppendLine(Resources.TsDetailsVM_HashLengthNotEqual);
            else if (!CompareTwoHashImprints(sourceFileImprint.Hash, tsToken.TimeStampInfo.GetMessageImprintDigest()))
                errMsg.AppendLine(Resources.TsDetailsVM_MessageImprintNonEqual);
            return errMsg.ToString();
        }

        private string ValidateInputValues()
        {
            FileInfo tsFileInfo = new FileInfo(TimeStampFilePath);
            FileInfo sourceFileInfo = new FileInfo(SourceFilePath);

            StringBuilder errMessage = new StringBuilder();
            if (string.IsNullOrWhiteSpace(TimeStampFilePath))
                errMessage.AppendLine(Resources.VerifyTsVM_TsFileNotInserted);
            if (string.IsNullOrWhiteSpace(SourceFilePath))
                errMessage.AppendLine(Resources.VerifyTsVM_SourceFileNotInserted);
            if (!tsFileInfo.Exists)
                errMessage.AppendLine(Resources.VerifyTsVM_TsFileBadPath);
            if (!sourceFileInfo.Exists)
                errMessage.AppendLine(Resources.VerifyTsVM_SourceFileBadPath);

            return errMessage.ToString();
        }

        private bool CompareTwoHashImprints(byte[] hash1, byte[] hash2)
        {
            for(int i = 0; i < hash1.Length; i++)
            {
                if (hash1[i] != hash2[i])
                    return false;
            }
            return true;
        }

        private bool IsTimeStampResponse(byte[] data)
        {
            try
            {
                TimeStampResponse response = new TimeStampResponse(data);
                return true;
            }
            catch
            {
                return false;
            }
        }

        private bool IsTimeStampToken(byte[] data)
        {
            try
            {
                TimeStampToken token = new TimeStampToken(new Org.BouncyCastle.Cms.CmsSignedData(data));
                return true;
            }
            catch
            {
                return false;
            }
        }

        private void AddTimeStampToList(TimeStampResponse timeStampResponse, FileInfo tsFileInfo)
        {
            TimeStampReqRes a = new TimeStampReqRes();
            a.SourceFilePath = SourceFilePath;
            a.TimeStampRequest = null;
            a.TimeStampResponse = timeStampResponse.GetEncoded();
            a.TimeStampFileType = TimeStampReqRes.FileTypeResponse;
            a.TimeStampFilePath = tsFileInfo.FullName;
            a.TimeStampFileName = tsFileInfo.Name;
            a.GenTime = timeStampResponse.TimeStampToken.TimeStampInfo.GenTime.ToString("yyyy/MM/dd HH:mm:ss");
            Manager.Instance.TimeStampsManager.AddTimeStamp(a);
            Manager.Instance.TimeStampsManager.SerializeTimeStamps();
            log.Info($"Timestamp {tsFileInfo.Name} added to TimeStamp history collection");
        }

        private void AddTimeStampToList(TimeStampToken timeStampToken, FileInfo tsFileInfo)
        {
            TimeStampReqRes a = new TimeStampReqRes();
            a.SourceFilePath = SourceFilePath;
            a.TimeStampRequest = null;
            a.TimeStampResponse = timeStampToken.GetEncoded();
            a.TimeStampFileType = TimeStampReqRes.FileTypeToken;
            a.TimeStampFilePath = tsFileInfo.FullName;
            a.TimeStampFileName = tsFileInfo.Name;
            a.GenTime = timeStampToken.TimeStampInfo.GenTime.ToString("yyyy/MM/dd HH:mm:ss");
            Manager.Instance.TimeStampsManager.AddTimeStamp(a);
            Manager.Instance.TimeStampsManager.SerializeTimeStamps();
            log.Info($"Timestamp {tsFileInfo.Name} added to TimeStamp history collection");
        }

        #endregion

    }
}
