using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Net.Http;
using System.Text;
using System.Data.SqlClient;
using System.IO;
using RnD.Common.Enums;
using RnD.Model;
using RnD.Model.EF;
using NLog;
using IntegraitonR3.Cryptography;
using IntegraitonR3.Models;
using Newtonsoft.Json;


namespace IntegraitonR3.Commands
{
    /// <summary>
    /// A set of commands for R3 integration
    /// </summary>  
    internal static class R3Commads
    {
        /// <summary>
        /// Filling in the data model R3
        /// </summary>
        /// <param name="sample">Sampe model</param>
        /// <param name="pasportLayoutTable">Pasport layout table model</param>
        /// <param name="passportMainTables">Passport main table models</param>
        /// <param name="databaseContext">Database context</param>
        /// <returns></returns>
        internal static R3 FillRThree(ISample sample, PassportLayoutTable pasportLayoutTable, List<PasportMainTable> passportMainTables, RnDConnection databaseContext)
        {
            #region Initializing required variables
            #region Tables
            var rndvAu = databaseContext.RndvAu.ToList();
            var rndvPrAu = databaseContext.RndvPrAu.ToList();
            #endregion

            var infoCardAC = sample.InfoCards.FirstOrDefault(c => c.ShortDescription == "ICQDataSheet");

            //Find status current
            var ssCurent = databaseContext.RndvSs.Find(1000011);
            //Find status historic
            var ssHistoric = databaseContext.RndvSs.Find(1000004);
            // Find attribute R3
            var R3Attribute = rndvAu.Where(c => c.SHORT_DESC == "R3")
                                    .OrderByDescending(c => c.VERSION)
                                    .FirstOrDefault();
            //Find attribute KSSS
            var KSSSAttribute = rndvAu.Where(c => c.SHORT_DESC == "KSSS_pr")
                                      .OrderByDescending(c => c.VERSION)
                                      .FirstOrDefault();

            var ksssProduct = infoCardAC?.InfoFields?.FirstOrDefault(c => c.Identifier == "Ksss")?.InfoFieldValue;
            #endregion

            var rThree = new R3
            {
                Idpcmag = 0, //Hardcode by according to the technical task
                IdPNPMag = 0, //Hardcode by according to the technical task
                KSSS = ksssProduct.NullOrEmptyFormatter(),
                ProductCode = 0, //Hardcode by according to the technical task
                ProductName = pasportLayoutTable.productName.NullOrEmptyFormatter(),
                PassportIssueDate = pasportLayoutTable.passportIssueDate.NullOrEmptyFormatter(),
                PassportNumber = pasportLayoutTable.passportNumber.NullOrEmptyFormatter(),
                DocumentType = "P", //Hardcode by according to the technical task
                DocumentMode = 3, //Hardcode by according to the technical task
                SampleCode = 0, //Hardcode by according to the technical task
                Status = "U", //Hardcode by according to the technical task
                FullName = pasportLayoutTable.userFullName.NullOrEmptyFormatter(),
            };

            foreach (var passportMainTable in passportMainTables)
            {
                //Search for all parameters of different versions
                var parameters = databaseContext.RndvPr.ToList()
                                                       .Where(c => c.PR == passportMainTable.PA)
                                                       .ToList();

                //Choice right parameter with Active = true and SS = Current, or choice last parameter where SS = Historic
                var selectedParameter = parameters.FirstOrDefault(c => c.ACTIVE == NullableBool.True && c.SS == ssCurent.SS) ??
                                        parameters.OrderByDescending(c => c.VERSION)
                                                  .FirstOrDefault(c => c.SS == ssHistoric.SS);
                //Get model linked attribute R3 to parameter
                var parameterAttributeR3 = rndvPrAu.Where(c => c.PR == selectedParameter.PR && c.AU == R3Attribute.AU)
                                                   .OrderByDescending(c => c.VERSION)
                                                   .FirstOrDefault();
                //Checking for the presence of parameter
                if (parameterAttributeR3 == null || selectedParameter == null) continue;
                //Get model linked attribute R3 to parameter    
                var parameterAttributeKSSS = rndvPrAu.Where(c => c.PR == selectedParameter.PR && c.AU == KSSSAttribute.AU)
                                                     .OrderByDescending(c => c.VERSION)
                                                     .FirstOrDefault();
                //Unit search
                var unit = databaseContext.RndvUnit.ToList()
                                                   .FirstOrDefault(c => c.UNIT_MAIN == selectedParameter.UNIT);
                //Search for the Russian name of the unit
                var unitLang = databaseContext.RndvUnitLang.Find(unit.UNIT_ID, 2);
                //Search for the unit ksss id by the unit id
                var unitKSSSId = (decimal)databaseContext.Database
                                                         .SqlQuery<int>($"SELECT Id FROM [RndSuite].[RndaUnitKSSS] WHERE [RndtUnitId] = @UnitId",
                                                          new SqlParameter("@UnitId", unit.UNIT_ID))
                                                         .FirstOrDefault();
                //
                rThree.Qualities.Add(new R3Qualitiy
                {
                    NumberParameter = passportMainTable.number,
                    Iduquality = parameterAttributeKSSS?.VALUE.NullOrEmptyFormatter(),
                    NameQuality = passportMainTable.DESCRIPTION_RUS.NullOrEmptyFormatter(),
                    NameUnit = unitLang?.UNIT.NullOrEmptyFormatter(),
                    SampleValue = passportMainTable.VALUE_RUS.NullOrEmptyFormatter(),
                    UnitKSSSId = (int)unitKSSSId,
                    NormativeValue = passportMainTable.NORM_STO_RUS.NullOrEmptyFormatter(),
                    CorrespondsNorm = 2, //Hardcode by according to the technical task
                });
            }

            return rThree;
        }
        /// <summary>
        /// Sending json to the data bus (KSSH)
        /// </summary>
        /// <param name="logger">Nlog logger</param>
        /// <param name="rThree">R3 data model</param>
        /// <param name="authInfo">Authorization data model</param>
        /// <param name="sampleId">Sample id</param>
        /// <returns>KeyValuePair of senging status messages, where the key is a short description and the value is a detailed one</returns>
        internal static KeyValuePair<string,string> SendPostRequestKssh(Logger logger, R3 rThree, RndaAuthorization authInfo, decimal? sampleId)
        {
            var json = JsonConvert.SerializeObject(rThree);
            var content = new StringContent(json, Encoding.UTF8, "application/json");
            var messages = new KeyValuePair<string, string>();
            using (var httpClient = new HttpClient())
            {
                httpClient.DefaultRequestHeaders.Authorization = BasicAuthorization(authInfo.Login, authInfo.Password);
                try
                {
                    var result = httpClient.PostAsync(authInfo.Url, content).Result;
                    if (!result.IsSuccessStatusCode)
                    {
                        messages = new KeyValuePair<string, string>("Failed", $"Отправка данных не удалась.\n{result.StatusCode}.");
                        logger.Error(new Exception { Source = "IntegrationR3" }, $"Failed to send request KSSH, error code: {result.StatusCode}, sample id: {sampleId}");
                    }
                    else
                    {
                        messages = new KeyValuePair<string, string>("Success", $"Отправка прошла успешно.\n{result.StatusCode}");
                        logger.Info($"Succesfully to send request KSSH, response code: {result.StatusCode}, sample id: {sampleId}");
                    }
                }
                catch (Exception ex)
                {
                    messages = new KeyValuePair<string, string>("Failed", $"Отправка данных не удалась.\nОшибка в программе: {ex.Message}.");
                    logger.Error(ex, $"Failed to to send request KSSH, sample id: {sampleId}");
                }
            }

            return messages;
        }
        /// <summary>
        /// Getting authorization data model
        /// </summary>
        /// <param name="databaseContext">Database context</param>
        /// <returns>Authorization data model</returns>
        /// <exception cref="Exception">Error about the absence of authorization data in the database</exception>
        internal static RndaAuthorization GetAuthorizationData(RnDConnection databaseContext)
        {
            var authInfo = databaseContext.Database.SqlQuery<RndaAuthorization>($"SELECT * FROM [RndSuite].[RndaAuthorization]").FirstOrDefault();
            if (authInfo == null) throw new Exception("Missing auth data for web api");
            var key = Encoding.UTF8.GetBytes(Properties.Resources.Key);
            var IV = new byte[16];
            string login = Convert.FromBase64String(authInfo.Login).DecryptStringFromBytesAes(key, IV);
            string password = Convert.FromBase64String(authInfo.Password).DecryptStringFromBytesAes(key, IV);
            return new RndaAuthorization { Login = login, Password = password, Url = authInfo.Url };
        }
        /// <summary>
        /// Recording the status of sending to the info card
        /// </summary>
        /// <param name="sample">Sample data model</param>
        /// <param name="message">Message for recording </param>
        internal static void WriteSendStatus(ISample sample, string message)
        {
            //Info field where the result of sending a message to KSSH will be written
            var infoCard = sample.InfoCards.FirstOrDefault(c => c.ShortDescription == "ICQDataSheet");
            var infoField = infoCard.InfoFields?.FirstOrDefault(c => c.Identifier == "R3SendStatus");

            if (infoField != null) infoField.InfoFieldValue = message;
        }
        /// <summary>
        /// Serialization of the data model R3 in json format
        /// </summary>
        /// <param name="rThree">R3 data model</param>
        internal static void JsonSerialization(R3 rThree)
        {
            string saveFolderPath = $@"{Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)}/JsonR3Files";
            if (!Directory.Exists(saveFolderPath)) Directory.CreateDirectory(saveFolderPath);
            string saveFilePath = $@"{saveFolderPath}/R3_{rThree.PassportNumber}.json";
            JsonSerializer jsonSerializer = new JsonSerializer();
            jsonSerializer.NullValueHandling = NullValueHandling.Ignore;
            using (StreamWriter sw = new StreamWriter(saveFilePath))
            using (JsonWriter writer = new JsonTextWriter(sw) { Formatting = Formatting.Indented })
                jsonSerializer.Serialize(writer, rThree);
        }
        /// <summary>
        /// Add counting point into R3 data model
        /// </summary>
        /// <param name="rThree">R3 data model</param>
        internal static void AddCountingPoint(ref R3 rThree)
        {
            rThree.Qualities.Add(new R3Qualitiy
            {
                Iduquality = "3435",
                NameQuality = "Точка учета",
                UnitKSSSId = 59,
                SampleValue = "1318",
                CorrespondsNorm = 0,
                NameUnit = "0",
                NormativeValue = "0",
                NumberParameter = 0,
            });
        }
        /// <summary>
        /// Add sampling place into R3 data model
        /// </summary>
        /// <param name="rThree">R3 data model</param>
        /// <param name="batchNumber">Bath number</param>
        internal static void AddSamplingPlace(ref R3 rThree, string batchNumber)
        {
            rThree.Qualities.Add(new R3Qualitiy
            {
                Iduquality = "ZQ_T",
                NameQuality = "Место отбора",
                UnitKSSSId = 59,
                SampleValue = batchNumber,
                CorrespondsNorm = 0,
                NameUnit = "0",
                NormativeValue = "0",
                NumberParameter = 0,
            });
        }
        /// <summary>
        /// Basic authorization
        /// </summary>
        /// <param name="login">Login</param>
        /// <param name="password">Password</param>
        /// <returns>Authentication header value</returns>
        private static AuthenticationHeaderValue BasicAuthorization(string login, string password)
        {
            var authenticationString = $"{login}:{password}";
            var base64EncodedAuthenticationString = Convert.ToBase64String(Encoding.ASCII.GetBytes(authenticationString));
            return new AuthenticationHeaderValue("Basic", base64EncodedAuthenticationString);
        }
        /// <summary>
        /// Formatting empty values in a string
        /// </summary>
        /// <param name="line">string</param>
        /// <returns>Formatted string</returns>
        private static string NullOrEmptyFormatter(this string line) => string.IsNullOrEmpty(line) || line == "-" ? "0" : line;
    }
}
