using System;
using System.Data;
using System.Threading;
using System.Threading.Tasks;
using DataservCycle;
using DataservEngine3;
using DataservEngine3.Components.Tags;
using DataservEngine3.Components.Communications;
using DataservEngine3.DataservData;
using DataservPlugin;
using System.Linq;
using System.Data.SqlClient;
using System.Collections.Generic;


namespace DataservExamples
{
    using System.Windows.Forms;

    [PluginData(Name = "Carrier-MX-MES", Author = "Tim Dekker / Justin Gamble", Description = "Carrier MX uses Database Stored Procedures for Interaction - See Documentation @ `H:\\Appliance\\Customer\\Carrier\\Carrier Mexico Sanata Catarina\\Dataserv Integration`.")]
    public class CarrierMxMes : AbstractPlugin
    {
        bool _debug = true;

        private static string _carrierImportTagGroup = "Carrier_MX_MES_Info";
        private static string _carrierImportErrorCode = "Error_Code";//0 = no error
        private static string _carrierImportErrorResponse = "Error_Response";//text

        private static string _carrierImportLastImportTimeName = "LastImportTime";

        private static DateTime _carrierImportLastImportTime = DateTime.MinValue;
        private short _lastChargingFlag = 0;
        private bool _hasPromptedMesFlag = false;

        //TODO: Update with what the given system actually uses, error handle if its a single ref system.
        private static Dictionary<string, Dictionary<string, string>> _carrierImportPartTranslation = new Dictionary<string, Dictionary<string, string>>
        {
            {
                "Refrigerant", new Dictionary<string, string>
                {
                    {"134", "134"},
                    {"513", "513"},
                    {"410", "410"},
                    {"R-410A", "410"},
                    {"PS40-10A", "410"},
                    {"PS40-54A", "454"},
                    //PS40-10A
                    {"454", "454"},
                    //MX A - may not be correct in production
                    {"9","410" },
                    {"8","454" }
                }
            }
        };

        private object _tagUpdateLock = new object();

        private static CancellationTokenSource _cancelSource = new CancellationTokenSource();

        private static Task _carrierImportImportTask;


        //Production Mode dictates if it is the "Residential" or "Commercial" version
        //Residential (SPP & SRT) = 1 - Badge, Cascade, Charging Params, Save
        //Commercial (LCV) = 2 - Badge, Cascade, Charging Params 1, "Save", Charging Params 2, "Save" 
        //MX A = 3 - MasterKey, Params, "Save"
        //TODO: Environment Flag
        private static int _carrierMesProductionMode = 2;

        private static string _carrierMesAppConfigPath = @"c:\siq\App.Config.XML";

        private static int _carrierMesAppChainId = -1;
        private static string _carrierMesAppLine = "";
        private static int _carrierMesAppWorkStationId = -1;

        private static string _carrierMesConnectionString1 = "database=RLCSC_MES;server=dataservtest.siqinc.com;Application Name=DataServ.Net 3.0;Pwd=Dataserv1;User ID=serviquip";

        private static string _carrierMesChainValue = "";
        private static string _carrierMesLine = "";
        private static string _carrierMesWorkStation = "";

        //jrg 2023-10-12 MX A has an additional app.config flag for recipe filtering
        private static string _carrierMesLimitTypeID = "";

        private const string CanRunUnit = "CanRunUnit";
        private const string CanOverrideReworkUnit = "CanOverrideReworkUnit";

        public override void OnEngineLoaded()
        {

            if (_debug) AddLogItem("Carrier-MX-MES - DEBUG - Assemblies Loaded.");

            InitializeTags();

            LoadEnvironmentItems();
        }

        private bool CurrentUserHasPermission(
            string userName,
            string permission
        )
        {
            var user = Context.SecurityManager.GetUser(userName);
            var result = user != null && user.ChallengePermissions(permission);
            //if the user failed to challenge, let them know
            if (!result)
            {
                AddLogItem($"User {userName} did not have permissions to {permission}.");
                AddNotificationPrompt($"El usuario actual no tenía permisos para {permission}");
            }
            return result;
        }

        public override bool OnScanItemsValidated(
            DataservEngine3.ScanItemCollection scanItems
        )
        {
            _hasPromptedMesFlag = false;

            if (_carrierMesProductionMode == 3)
            {
                //MX A does not do operator validation.
                return true;
            }

            if (!CurrentUserHasPermission(scanItems["Operator"].Value, CanRunUnit)) return false;
            //If they turned it to local mode, bypass fillin and do not contact MES
            if (_carrierMesProductionMode == 0)
            {
                scanItems["Model"].Value = scanItems["Serial Number"].Value;
                AddLogItem($"Model set to {scanItems["Model"].Value} for bypass.");
            }
            return true;
        }

        public override bool ShouldSkipCircuit(
            ScanItemCollection scanItems,
            Circuit circuit
        )
        {
			 if (_carrierMesProductionMode == 0)
            {
				return false;
			}
            //get circuit number
            var isCircuit2 = circuit.Name.EndsWith("2");
            var circuitNumber = isCircuit2 ? "2" : "1";


            //jgamble handle bad ref types as skip
            var recipeFillTypeName = "PresetFillType";
            var refTypeSelected = GetRecipeItemValue(circuit.ActiveRecipe, recipeFillTypeName);

            if (refTypeSelected == "0")
            {
                SetError(17, "Refrigerant Type is Not Translated Correctly.");
                AddNotificationPrompt($"Incorrecta @vcRefType");
                return true;
            }
            try
            {
                var recipeFillQtyName = $"PresetFill{circuitNumber}Quantity";
                var refQtySelected = GetRecipeItemValue(circuit.ActiveRecipe, recipeFillQtyName);

                if (string.IsNullOrEmpty(refQtySelected) || refQtySelected == "0")
                {

                    return true;
                }
            }
            catch (Exception ex)
            {
                SetError(17, "Refrigerant Type is Not Translated Correctly.");
                AddNotificationPrompt($"Incorrecta @nRefQuanty {circuitNumber} ");
                return true;
            }

            //allow 'local' mode to run un-checked
            //jrg 2023-10-12 - to make engineers happy for in house run off
            //we still want quantity parsing to allow skip, but ignore any "previous flag".
            if (_carrierMesProductionMode == 0) return false;

            //MX does not do operator valdiation or override
            if (_carrierMesProductionMode == 3) return false;

            if (_lastChargingFlag == 1 && !_hasPromptedMesFlag)
            {
                AddLogItem($"MES requested a CanCharge Flag of 1, asking for override...");
                var mesFlagResult = ShowUserPrompt($"El sistema MES ha marcado este escaneo como que no se puede cargar. ¿Le gustaría reintentar de todos modos?", Prompt.PromptTypes.PasswordYes_No)
                                   .GetAwaiter()
                                   .GetResult();
                var returnValue = (DialogResult)mesFlagResult.Answer == DialogResult.No || !CurrentUserHasPermission(mesFlagResult.User.ToString(), CanOverrideReworkUnit);

                _hasPromptedMesFlag = true;

                return returnValue;
            }


            //get table and run query
            var table = Context.DataservTables["ChargerOutput"];
            var query = table.Database.QuickQuery($"SELECT COUNT(*) From [Dataserv].[dbo].{table.DatabaseName} WHERE [Serial] = '{scanItems["Serial"].Value}' AND FinalDataCompletionCode_AC1 = 1 AND CircuitNumber = {circuitNumber}");
            AddLogItem($"For circuit {circuitNumber}, got back {query.Tables[0].Rows[0][0].ToString()} from the query.");
            //if zero rows, return false -- do not skip
            if (query.Tables[0]
                     .Rows[0][0]
                     .ToString() ==
                "0") return false;
            //if you get to here, there was a previous pass, need to ask for override
            AddLogItem($"Circuit {circuitNumber} was potentially skipped due to previous charges, asking for override...");
            var result = ShowUserPrompt($"Circuito {circuitNumber} se detectó que tenía cargos previos. Haga clic en Sí para continuar cargando, No para omitir.", Prompt.PromptTypes.PasswordYes_No)
                        .GetAwaiter()
                        .GetResult();
            //per the above, if the answer is NO, result would be true, meaning we would skip, any other answer would be false (think YES) and we would continue to charge
            return (DialogResult)result.Answer == DialogResult.No || !CurrentUserHasPermission(result.User.ToString(), CanOverrideReworkUnit);
        }

        private void InitializeTags()
        {
            var requireSave = false;
            //check for the base groups and tags to working taglist
            try
            {
                //Groups
                if (Context.DataservTaglist.TagGroups[_carrierImportTagGroup] == null)
                {
                    Context.DataservTaglist.TagGroups.Add(new TagGroup(_carrierImportTagGroup, "", 1000, ""));
                    requireSave = true;

                }
                //Tags
                if (Context.DataservTaglist.Tag(_carrierImportTagGroup, _carrierImportErrorCode) == null)
                {

                    Context.DataservTaglist
                           .TagGroups[_carrierImportTagGroup]
                           .Tags
                           .Add(new Tag(_carrierImportErrorCode, _carrierImportTagGroup, "N/A", Tag.Tag_Type.STR, -1, ""));
                    requireSave = true;
                }

                if (Context.DataservTaglist.Tag(_carrierImportTagGroup, _carrierImportErrorResponse) == null)
                {
                    Context.DataservTaglist
                           .TagGroups[_carrierImportTagGroup]
                           .Tags
                           .Add(new Tag(_carrierImportErrorResponse, _carrierImportTagGroup, "N/A", Tag.Tag_Type.STR, -1, ""));

                    requireSave = true;
                }

                if (Context.DataservTaglist.Tag(_carrierImportTagGroup, _carrierImportLastImportTimeName) == null)
                {

                    Context.DataservTaglist
                           .TagGroups[_carrierImportTagGroup]
                           .Tags
                           .Add(new Tag(_carrierImportLastImportTimeName, _carrierImportTagGroup, "N/A", Tag.Tag_Type.STR, -1, ""));

                    requireSave = true;
                }
                //Set all managed tags to 0 initial value
                foreach (Tag t in Context.DataservTaglist.TagGroups[_carrierImportTagGroup].Tags.Values)
                {

                    AddLogItem($"Accessing '{t.FullName}' with value '{t.Value}', Attempting to set to '0'");

                    t.Value = "";

                }
                //we just need this one set to 0 for logic purposes
                Context.DataservTaglist.Tag(_carrierImportTagGroup, _carrierImportErrorCode)
                       .Value = 0;
            }
            catch (Exception ex)
            {
                AddLogItem($"Failled to Add Groups and Tags. {ex.Message}");
            }

        }



        private void LoadEnvironmentItems()
        {
            if (Context.DataservEnvironmentItems["CarrierMES_ProductionMode"] != null)
            {
                var envProdMode = 0;
                if (int.TryParse(Context.DataservEnvironmentItems["CarrierMES_ProductionMode"]
                                        .Value
                                        .ToString()
                                        .Trim(),
                                 out envProdMode
                    ))
                {
                    _carrierMesProductionMode = envProdMode;
                }
                //update with live changes
                var envProdModeTag = Context.DataservTaglist.Tag("TAGGROUP::Dataserv Environment Items::TAG::CarrierMES_ProductionMode");

                if (envProdModeTag != null && int.TryParse(envProdModeTag.Value.ToString(), out envProdMode))
                {
                    _carrierMesProductionMode = envProdMode;
                }
            }
            if (Context.DataservEnvironmentItems["CarrierMES_ConnectionString"] != null)
            {
                if (Context.DataservEnvironmentItems["CarrierMES_ConnectionString"]
                           .Value
                           .ToString()
                           .Trim() !=
                    string.Empty)
                {
                    _carrierMesConnectionString1 = Context.DataservEnvironmentItems["CarrierMES_ConnectionString"]
                                                          .Value
                                                          .ToString()
                                                          .Trim();
                }
                //update with live changes
                var envConStringTag = Context.DataservTaglist.Tag("TAGGROUP::Dataserv Environment Items::TAG::CarrierMES_ConnectionString");
                if (envConStringTag != null && envConStringTag.Value.ToString() != string.Empty)
                {
                    _carrierMesConnectionString1 = envConStringTag.Value.ToString();
                }
            }
            if (_debug) AddLogItem($"Carrier-MX-MES - Environment Items Loaded: Production Mode: {_carrierMesProductionMode}");
        }


        //Carrier wants us to try to update at scan time as well, if we fail to hit their end point, just use the cache.
        //cache version should already be loaded because that's what the recipes are now.
        public override bool OnRecipeRead(
            ref Recipe recipe,
            Circuit circuit,
            ScanItemCollection scanItems,
            ref string response,
            ref Request request
        )
        {
            ClearError();
            try
            {
                LoadEnvironmentItems();
                UpdateAppConfig();
                try
                {
                    //which process are we handling?
                    switch (_carrierMesProductionMode)
                    {
                        case 0:
                            AddLogItem($"Bypassing MES for local");
                            return true;
                        case 1:
                            ProcessRecipeRead_Res_SPP_SRT(ref recipe, circuit, scanItems, ref response, ref request);
                            break;
                        case 2:
                            ProcessRecipeRead_LCV(ref recipe, circuit, scanItems, ref response, ref request);
                            break;
                        case 3:
                            ProcessRecipeRead_MXA(ref recipe, circuit, scanItems, ref response, ref request);
                            break;
                        default:
                            SetError(10, "Production Mode Set Incorrectly. Check Env.");
                            break;
                    }

                    if (response != string.Empty)
                    {
                        return false;
                    }
                }
                catch (Exception ex)
                {
                    AddLogItem("Carrier-MX-MES - Error Updating On Recipe Read!", ex);
                    return false;
                }
            }
            catch (Exception ex2)
            {
                AddLogItem("Carrier-MX-MES - Error Updating On Recipe Read!", ex2);
                return false;
            }

            return true;
        }

        public override void OnCircuitComplete(
            Circuit circuit
        )
        {
            LoadEnvironmentItems();
            var response = "";

            //which process are we handling?
            switch (_carrierMesProductionMode)
            {
                case 0:
                    AddLogItem("Bypassing MES for local");
                    break;
                case 1:
                    ProcessCircuitComplete_Res_SPP_SRT(circuit, ref response);
                    break;
                case 2:
                    ProcessCircuitComplete_LCV(circuit, ref response);
                    break;
                case 3:
                    ProcessCircuitComplete_MXA(circuit, ref response);
                    break;
                default:
                    SetError(10, "Production Mode Set Incorrectly. Check Env.");
                    break;
            }

            AddLogItem($"Carrier-MX-MES - CircuitCompleteResponse : `{response}`");
        }

        private void SetRecipeItem(
            ref Recipe recipe,
            string fieldName,
            object value
        )
        {
            try
            {
                if (recipe.RecipeFields[fieldName] != null)
                {
                    recipe.RecipeFields[fieldName].Value = value;
                }
                //update the recipe table values, these can be retrieved for Dataserv internal use (enables)
                if (recipe.RecipeTableValues.Keys.Contains(fieldName))
                {
                    recipe.RecipeTableValues[fieldName] = value;
                }
                else
                {
                    recipe.RecipeTableValues.Add(fieldName, value);
                }
            }
            catch (Exception ex)
            {
                AddLogItem($"Carrier-MX-MES - Error in Recipe Set for '{fieldName}' with '{value.ToString()}'", ex);
            }

        }

        private string GetRecipeItemValue(
            Recipe recipe,
            string fieldName
        )
        {
            if (recipe.RecipeTableValues.Keys.Contains(fieldName))
            {
                return recipe.RecipeTableValues[fieldName]
                             .ToString();
            }
            return string.Empty;
        }

        //check if the fluid type is good for this system
        //if not the calling method needs to report, bad result = 0.
        private void ValidateFluidType(
            string unitfluidtype,
            out decimal resultfluidtype
        )
        {
            resultfluidtype = 0;
            try
            {

                if (_carrierImportPartTranslation["Refrigerant"]
                   .ContainsKey(unitfluidtype))
                {
                    resultfluidtype = Convert.ToDecimal(_carrierImportPartTranslation["Refrigerant"][unitfluidtype]);
                }
            }
            catch (Exception ex)
            {

            }

        }

        private void SetError(
            int eCode,
            string eResponse
        )
        {
            try
            {

                var errorCodeTag = Context.DataservTaglist.Tag(_carrierImportTagGroup, _carrierImportErrorCode);
                var errorResponseTag = Context.DataservTaglist.Tag(_carrierImportTagGroup, _carrierImportErrorResponse);

                errorCodeTag.Value = $"{eCode}";
                errorResponseTag.Value = eResponse;

                AddLogItem($"Carrier-MX-MES - SetError: '{eCode.ToString()}' - '{eResponse}'");

            }
            catch (Exception ex)
            {
                AddLogItem("Carrier-MX-MES - Error Updating Status!", ex);
            }

        }

        private void ClearError()
        {
            try
            {

                var errorCodeTag = Context.DataservTaglist.Tag(_carrierImportTagGroup, _carrierImportErrorCode);
                var errorResponseTag = Context.DataservTaglist.Tag(_carrierImportTagGroup, _carrierImportErrorResponse);

                errorCodeTag.Value = $"0";
                errorResponseTag.Value = "";

            }
            catch (Exception ex)
            {
                AddLogItem("Carrier-MX-MES - Error Clearing Status!", ex);
            }

        }

        /// <summary>
        /// Where the start cycle comes together for Residential SPP & SRT Chargers.
        /// * Operator Valid from Scan Item - Operator.
        /// * Cascade Valid from Scan Item - Scan, CascadeFlag from OperatorValid.
        /// 
        /// </summary>
        /// <param name="recipe"></param>
        /// <param name="circuit"></param>
        /// <param name="scanItems"></param>
        /// <param name="response"></param>
        /// <param name="request"></param>
        private void ProcessRecipeRead_Res_SPP_SRT(
            ref Recipe recipe,
            Circuit circuit,
            ScanItemCollection scanItems,
            ref string response,
            ref Request request
        )
        {

            const string recipeFillTypeName = "PresetFillType";
            const string spFillTypeParmName = "@vcRefType";//need translate, no examples

            const string recipeFillQty1Name = "PresetFill1Quantity";
            const string spFillQty1ParmName = "@nRef_Weight";//need unit convert kg -> g

            const string recipeFillQty2Name = "PresetFill2Quantity";
            const string spFillQty2ParmName = "@nRef_Weight2";//need unit convert kg -> g

            const string recipeCircuitTypeName = "CircuitStyle";

            var hasCharge1 = false;
            var hasCharge2 = false;


            try
            {

                var serialScanValue = scanItems["Serial"].Value;
                var resOperatorValid = ExecuteSP_OperatorValid(scanItems["Operator"].Value);
                var passOperatorValid = ValidateSqlResult(resOperatorValid);

                SqlResultIntoRecipe(resOperatorValid, ref recipe);
                if (passOperatorValid)
                {
                    var resMasterKeyValid = ExecuteSP_MasterKey_CascadeWSValid(Convert.ToBoolean(resOperatorValid["@CascadeFlag"].Value), serialScanValue, Convert.ToInt16(resOperatorValid["@iSucc_Code"].Value));
                    var passMasterKeyValid = ValidateSqlResult(resMasterKeyValid);
                    _lastChargingFlag = Convert.ToInt16(resMasterKeyValid["@ChargingFlag"].Value);
                    SqlResultIntoRecipe(resMasterKeyValid, ref recipe);
                    if (passMasterKeyValid)
                    {
                        var resChargingParams = ExecuteSP_Charging_Params(serialScanValue);
                        var passChargingParams = ValidateSqlResult(resChargingParams);
                        if (passChargingParams)
                        {
                            var outFluidType = 0M;
                            ValidateFluidType(resChargingParams[spFillTypeParmName]
                                             .Value
                                             .ToString(),
                                              out outFluidType
                            );
                            SetRecipeItem(ref recipe, recipeFillTypeName, outFluidType);

                            var outFillQty = 0M;
                            var outFillQty2 = 0M;
                            if (decimal.TryParse(resChargingParams[spFillQty1ParmName]
                                                .Value
                                                .ToString(),
                                                 out outFillQty
                                ))
                            {
                                outFillQty *= 1000M;
                            }
                            SetRecipeItem(ref recipe, recipeFillQty1Name, outFillQty);
                            hasCharge1 = outFillQty > 0M;


                            if (decimal.TryParse(resChargingParams[spFillQty2ParmName]
                                                .Value
                                                .ToString(),
                                                 out outFillQty2
                                ))
                            {
                                outFillQty2 *= 1000M;//kg -> grams
                            }
                            SetRecipeItem(ref recipe, recipeFillQty2Name, outFillQty2);
                            hasCharge2 = outFillQty2 > 0M;

                            if (hasCharge1 && hasCharge2)
                            {
                                SetRecipeItem(ref recipe, recipeCircuitTypeName, 3);
                            }
                            else if (hasCharge1 && !hasCharge2)
                            {
                                SetRecipeItem(ref recipe, recipeCircuitTypeName, 1);
                            }
                            else if (!hasCharge1 && hasCharge2)
                            {
                                SetRecipeItem(ref recipe, recipeCircuitTypeName, 2);
                            }

                        }
                        else
                        {
                            response = "Charging Params Invalid";
                        }
                    }
                    else
                    {
                        response = "MasterKey Invalid";
                    }
                }
                else
                {
                    response = "Operator Invalid";
                }
            }
            catch (Exception ex)
            {
                response = "Process Failure - SPP/SRT";
                SetError(55, response);
                AddLogItem($"CarrierMX_MES - Error in SPP/SRT Process", ex);
            }
        }

        private void ProcessCircuitComplete_Res_SPP_SRT(
            Circuit circuit,
            ref string response
        )
        {
            try
            {
                if (_debug) AddLogItem($"CarrierMX_MES - Starting Circuit Complete Processing SPP/SRT.");

                var outputMapping = circuit.OutputMappings[0];
                var isCurrentUnitPass = false;
                if (!string.IsNullOrEmpty(outputMapping.DestinationTable.CodeField))
                {
                    var completionCodeField = outputMapping.FieldMappings
                                                           .Cast<FieldMapping>()
                                                           .FirstOrDefault(field => field.Destination.Name == outputMapping.DestinationTable.CodeField);
                    isCurrentUnitPass = Context.DataservTaglist
                                               .Tag(completionCodeField.Source)
                                               .Value
                                               .ToString() ==
                                        "1";
                }

                //push result to SP
                var serialScanValue = Context.RegularExpression
                                             .Parse($"RUNNINGCYCLE::{circuit.Cycle}::SCANITEM::Serial::SPECIAL::VALUE")
                                             .ToString();
                var operatorScanValue = Context.RegularExpression
                                               .Parse($"RUNNINGCYCLE::{circuit.Cycle}::SCANITEM::Operator::SPECIAL::VALUE")
                                               .ToString();
                var materialResult = GetRecipeItemValue(circuit.ActiveRecipe, @"vcMaterial");

                var resultValue = Convert.ToInt16(isCurrentUnitPass ? GetRecipeItemValue(circuit.ActiveRecipe, "@iSucc_Code") : GetRecipeItemValue(circuit.ActiveRecipe, "@iFailed_Code"));
                var resSaveResult = ExecuteSP_SaveResults(serialScanValue, resultValue, materialResult, operatorScanValue);
                var passSaveResults = ValidateSqlResult(resSaveResult);
                if (!passSaveResults)
                {
                    AddLogItem($"Unable to save results, returning sql was not valid: {resSaveResult}");
                }
            }
            catch (Exception ex)
            {
                response = "Process Failure - SPP/SRT";
                SetError(55, response);
                AddLogItem($"CarrierMX_MES - Error in SPP/SRT Process", ex);
            }
        }

        /// <summary>
        /// Where the start cycle comes together for LCV.
        /// * Operator Valid from Scan Item - Operator.
        /// * Cascade Valid from Scan Item - Scan, CascadeFlag from OperatorValid.
        /// 
        /// </summary>
        /// <param name="recipe"></param>
        /// <param name="circuit"></param>
        /// <param name="scanItems"></param>
        /// <param name="response"></param>
        /// <param name="request"></param>
        private void ProcessRecipeRead_LCV(
            ref Recipe recipe,
            Circuit circuit,
            ScanItemCollection scanItems,
            ref string response,
            ref Request request
        )
        {

            if (recipe == null)
            {
                AddLogItem($"CarrierMX_MES - Recipe for LCV is Null!");
            }
            var recipeFillTypeName = "PresetFillType";
            var spFillTypeParmName = "@vcRefType";//need translate, no examples

            var recipeFillQty1Name = "PresetFill1Quantity";
            var spFillQty1ParmName = "@nRefCharge_KG";//need unit convert kg -> g

            var recipeFillQty2Name = "PresetFill2Quantity";
            var spFillQty2ParmName = "@nRemainRefCharge_KG";//need unit convert kg -> g

            var recipeCircuitTypeName = "CircuitStyle";

            var hasCharge1 = false;
            var hasCharge2 = false;

            try
            {

                var serialScanValue = scanItems["Serial"].Value;
                var resOperatorValid = ExecuteSP_OperatorValid(scanItems["Operator"].Value);
                var passOperatorValid = ValidateSqlResult(resOperatorValid);
                SqlResultIntoRecipe(resOperatorValid, ref recipe);
                if (passOperatorValid)
                {
                    var resMasterKeyValid = ExecuteSP_MasterKey_CascadeWSValid(Convert.ToBoolean(resOperatorValid["@CascadeFlag"].Value), serialScanValue, Convert.ToInt16(resOperatorValid["@iSuccCode"].Value));
                    var passMasterKeyValid = ValidateSqlResult(resMasterKeyValid);
                    _lastChargingFlag = Convert.ToInt16(resMasterKeyValid["@ChargingFlag"].Value);
                    SqlResultIntoRecipe(resMasterKeyValid, ref recipe);

                    if (passMasterKeyValid)
                    {
                        var resChargingParams = ExecuteSP_LCV_Charging_Params(serialScanValue);
                        var passChargingParams = ValidateSqlResult(resChargingParams);
                        var resChargingParams2 = ExecuteSP_LCV_SecondCharging_Params(serialScanValue);
                        var passChargingParams2 = ValidateSqlResult(resChargingParams2);
                        var outFluidType = 0M;
                        var outFillQty = 0M;
                        var outFillQty2 = 0M;

                        if (passChargingParams)
                        {
                            ValidateFluidType(resChargingParams[spFillTypeParmName]
                                             .Value
                                             .ToString(),
                                              out outFluidType
                            );
                            SetRecipeItem(ref recipe, recipeFillTypeName, outFluidType);
                            if (decimal.TryParse(resChargingParams[spFillQty1ParmName]
                                                .Value
                                                .ToString(),
                                                 out outFillQty
                                ))
                            {
                                outFillQty *= 1000M;//kg -> grams
                            }
                            SetRecipeItem(ref recipe, recipeFillQty1Name, outFillQty);
                        }
                        else
                        {
                            SetRecipeItem(ref recipe, recipeFillTypeName, outFluidType);
                            SetRecipeItem(ref recipe, recipeFillQty1Name, outFillQty);
                        }

                        //It looks like for LCV if their database does not return a charge amount/require second charge, it actually reports as error, we want to just surpress that and continue on
                        if (passChargingParams2)
                        {
                            //only update this from charge 2 if charge 1 wasn't set
                            if (outFluidType == 0M)
                            {
                                ValidateFluidType(resChargingParams2[spFillTypeParmName]
                                                 .Value
                                                 .ToString(),
                                                  out outFluidType
                                );
                                SetRecipeItem(ref recipe, recipeFillTypeName, outFluidType);
                            }

                            if (decimal.TryParse(resChargingParams2[spFillQty2ParmName]
                                                .Value
                                                .ToString(),
                                                 out outFillQty2
                                ))
                            {
                                outFillQty2 *= 1000M;//kg -> grams
                            }
                            SetRecipeItem(ref recipe, recipeFillQty2Name, outFillQty2);
                        }
                        else
                        {
                            if (outFluidType != 0M)
                            {
                                //clear it so it doesn't show to operator, since this is normal?
                                ClearError();
                                //first charge was success, we want to preserve the fluid type
                            }
                            else
                            {
                                SetRecipeItem(ref recipe, recipeFillTypeName, outFluidType);
                            }
                            SetRecipeItem(ref recipe, recipeFillQty2Name, outFillQty2);
                        }
                        hasCharge1 = outFillQty > 0M;
                        hasCharge2 = outFillQty2 > 0M;

                        if (hasCharge1 && hasCharge2)
                        {
                            SetRecipeItem(ref recipe, recipeCircuitTypeName, 3);
                        }
                        else if (hasCharge1 && !hasCharge2)
                        {
                            SetRecipeItem(ref recipe, recipeCircuitTypeName, 1);
                        }
                        else if (!hasCharge1 && hasCharge2)
                        {
                            SetRecipeItem(ref recipe, recipeCircuitTypeName, 2);
                        }
                        else
                        {
                            response = "Charging Params Invalid";
                        }

                    }

                    else
                    {
                        response = "MasterKey Invalid";
                    }
                }
                else
                {
                    response = "Operator Invalid";
                }
            }
            catch (Exception ex)
            {
                response = "Process Failure - LCV";
                SetError(65, response);
                AddLogItem($"CarrierMX_MES - Error in SPP/SRT Process", ex);
            }
        }

        private void ProcessCircuitComplete_LCV(
            Circuit circuit,
            ref string response
        )
        {
            try
            {
                if (_debug) AddLogItem($"CarrierMX_MES - Starting Circuit Complete Processing LCV.");


                var outputMapping = circuit.OutputMappings[0];
                var isCurrentUnitPass = false;
                if (!string.IsNullOrEmpty(outputMapping.DestinationTable.CodeField))
                {
                    var completionCodeField = outputMapping.FieldMappings
                                                           .Cast<FieldMapping>()
                                                           .FirstOrDefault(field => field.Destination.Name == outputMapping.DestinationTable.CodeField);
                    isCurrentUnitPass = Context.DataservTaglist
                                               .Tag(completionCodeField.Source)
                                               .Value
                                               .ToString() ==
                                        "1";
                }
                //push result to SP
                var serialScanValue = Context.RegularExpression
                                             .Parse($"RUNNINGCYCLE::{circuit.Cycle}::SCANITEM::Serial::SPECIAL::VALUE")
                                             .ToString();
                var operatorScanValue = Context.RegularExpression
                                               .Parse($"RUNNINGCYCLE::{circuit.Cycle}::SCANITEM::Operator::SPECIAL::VALUE")
                                               .ToString();
                var materialResult = GetRecipeItemValue(circuit.ActiveRecipe, @"vcMaterial");

                var resultValue = Convert.ToInt16(isCurrentUnitPass ? GetRecipeItemValue(circuit.ActiveRecipe, "@iSucc_Code") : GetRecipeItemValue(circuit.ActiveRecipe, "@iFailed_Code"));
                var resSaveResult = ExecuteSP_SaveResults(serialScanValue, resultValue, materialResult, operatorScanValue);
                var passSaveResults = ValidateSqlResult(resSaveResult);
                if (!passSaveResults)
                {
                    AddLogItem($"Unable to save results, returning sql was not valid: {resSaveResult}");
                }
            }
            catch (Exception ex)
            {
                response = "Process Failure - LCV";
                SetError(65, response);
                AddLogItem($"CarrierMX_MES - Error in SPP/SRT Process", ex);
            }
        }

        //======
        //MX A process

        /// <summary>
        /// MX A uses parameters from us, so everything should be direct :crossed_fingers:
        /// </summary>
        /// <param name="recipe"></param>
        /// <param name="circuit"></param>
        /// <param name="scanItems"></param>
        /// <param name="response"></param>
        /// <param name="request"></param>
        private void ProcessRecipeRead_MXA(
            ref Recipe recipe,
            Circuit circuit,
            ScanItemCollection scanItems,
            ref string response,
            ref Request request
        )
        {

            const string recipeEvacL = "PresetEvacuationLevel";
            const string spEvacL = "@EvacL";

            const string recipeEvacT = "PresetEvacuationTime";
            const string spEvacT = "@EvacT";

            const string recipeVacuumL = "PresetVacuumCheckLevel";
            const string spVacuumL = "@VacuumL";

            const string recipeVacuumT = "PresetVacuumCheckTime";
            const string spVacuumT = "@VacuumT";

            //reject just uses regular evac
            const string recipeRejectEvacL = "PresetRejectEvacuationLevel";
            const string spRejectEvacL = "@EvacL";

            const string recipeRejectEvacT = "PresetRejectEvacuationTime";
            const string spRejectEvacT = "@EvacT";

            const string recipeFillTypeName = "PresetFillType";
            const string spFillTypeParmName = "@Coolant";//need translate, no examples

            const string recipeFillQty1Name = "PresetFill1Quantity";
            const string spFillQty1ParmName = "@ChargeQty";

            const string recipePercent = "PresetFillPercent";
            const string spPercent = "@Percent";

            var hasCharge1 = false;

            try
            {

                var serialScanValue = scanItems["Serial"].Value;

                var resMasterKeyValid = ExecuteSP_MXA_ValidateMK_Get(serialScanValue);
                var passMasterKeyValid = ValidateSqlResult(resMasterKeyValid);

                SqlResultIntoRecipe(resMasterKeyValid, ref recipe);

                if (passMasterKeyValid)
                {
                    var resChargingParams = ExecuteSP_MXA_RecetaMK_Get(serialScanValue);
                    var passChargingParams = ValidateSqlResult(resChargingParams);
                    if (passChargingParams)
                    {
                        var outFluidType = 0M;
                        ValidateFluidType(resChargingParams[spFillTypeParmName]
                                         .Value
                                         .ToString(),
                                          out outFluidType
                        );
                        SetRecipeItem(ref recipe, recipeFillTypeName, outFluidType);

                        var outFillQty = 0M;
                        if (decimal.TryParse(resChargingParams[spFillQty1ParmName]
                                            .Value
                                            .ToString(),
                                             out outFillQty
                            ))
                        {
                            //MX A stores value in grams, seemingly
                            //outFillQty *= 1000M;
                        }
                        SetRecipeItem(ref recipe, recipeFillQty1Name, outFillQty);

                        //this assumes they're passing us correct values. If it blows up check input/output from SP, probably a null or non-real
                        SetRecipeItem(ref recipe, recipeEvacL, resChargingParams[spEvacL].Value);
                        SetRecipeItem(ref recipe, recipeEvacT, resChargingParams[spEvacT].Value);
                        SetRecipeItem(ref recipe, recipeVacuumL, resChargingParams[spVacuumL].Value);
                        SetRecipeItem(ref recipe, recipeVacuumT, resChargingParams[spVacuumT].Value);
                        SetRecipeItem(ref recipe, recipeRejectEvacL, resChargingParams[spRejectEvacL].Value);
                        SetRecipeItem(ref recipe, recipeRejectEvacT, resChargingParams[spRejectEvacT].Value);

                        //may end up being slightly special?

                        SetRecipeItem(ref recipe, recipePercent, resChargingParams[spPercent].Value);


                    }
                    else
                    {
                        response = "Charging Params Invalid";
                    }
                }
                else
                {
                    response = "MasterKey Invalid";
                }

            }
            catch (Exception ex)
            {
                response = "Process Failure - MX A";
                SetError(55, response);
                AddLogItem($"CarrierMX_MES - Error in MX A Process", ex);
            }
        }

        private void ProcessCircuitComplete_MXA(
            Circuit circuit,
            ref string response
        )
        {
            try
            {
                if (_debug) AddLogItem($"CarrierMX_MES - Starting Circuit Complete Processing MX A.");

                var outputMapping = circuit.OutputMappings[0];
                var isCurrentUnitPass = false;
                if (!string.IsNullOrEmpty(outputMapping.DestinationTable.CodeField))
                {
                    var completionCodeField = outputMapping.FieldMappings
                                                           .Cast<FieldMapping>()
                                                           .FirstOrDefault(field => field.Destination.Name == outputMapping.DestinationTable.CodeField);
                    isCurrentUnitPass = Context.DataservTaglist
                                               .Tag(completionCodeField.Source)
                                               .Value
                                               .ToString() ==
                                        "1";
                }

                //push result to SP
                var serialScanValue = Context.RegularExpression
                                             .Parse($"RUNNINGCYCLE::{circuit.Cycle}::SCANITEM::Serial::SPECIAL::VALUE")
                                             .ToString();
                var operatorScanValue = Context.RegularExpression
                                               .Parse($"RUNNINGCYCLE::{circuit.Cycle}::SCANITEM::Operator::SPECIAL::VALUE")
                                               .ToString();
                var materialResult = GetRecipeItemValue(circuit.ActiveRecipe, @"vcMaterial");

                var resultValue = Convert.ToInt16(isCurrentUnitPass ? 1 : 0);
                var resSaveResult = ExecuteSP_MXA_SaveResults(serialScanValue, resultValue);
                var passSaveResults = ValidateSqlResult(resSaveResult);
                if (!passSaveResults)
                {
                    AddLogItem($"Unable to save results, returning sql was not valid: {resSaveResult}");
                }
            }
            catch (Exception ex)
            {
                response = "Process Failure - MX A";
                SetError(55, response);
                AddLogItem($"CarrierMX_MES - Error in MX A Process", ex);
            }
        }

        //END MX A process
        //======

        private SqlParameterCollection ExecuteStoredProcedure(
            string storedProcedureName,
            params SqlParameter[] spParams
        )
        {
            try
            {
                //query the database
                using (var cn = new SqlConnection(_carrierMesConnectionString1))
                {
                    AddLogItem($"CarrierMX_MES - Connected");
                    using (var cmd = new SqlCommand())
                    {
                        AddLogItem($"CarrierMX_MES - Created Command");
                        using (var da = new SqlDataAdapter(cmd))
                        {
                            cmd.Connection = cn;
                            cmd.CommandType = CommandType.StoredProcedure;
                            cmd.CommandText = storedProcedureName;
                            cmd.Parameters.AddRange(spParams);
                            cn.Open();
                            var res = cmd.ExecuteNonQuery();
                            //res not checked, but possibly needs to be
                            try
                            {
                                if (_debug) AddLogItem($"Carrier-MX-MES - SQL Result '{storedProcedureName}' with Input: '{string.Join(";", spParams.Select(parm => $"{parm.ParameterName} = {parm.Value.ToString()}"))}' - Result '{string.Join(";", (from SqlParameter resparm in cmd.Parameters select resparm).ToArray().Select(parm => $"{parm.ParameterName} = {parm.Value.ToString()}"))}'");
                            }
                            catch (Exception ex)
                            {
                                //we just want to log each SQL result for trouble shooting.
                            }
                            return cmd.Parameters;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                AddLogItem($"CarrierMX_MES - Issue Executing StoredProcedure", ex);
            }
            return null;
        }

        private SqlParameterCollection ExecuteSP_OperatorValid(string _operator)
        {
            var isAdmin = Context.SecurityManager.GetUser(_operator).IsAdmin;
            //NOTE: this was changed on 9/20/23 to now not use the operator as an argument
            var parms = new List<SqlParameter>();

            var idChainValue = new SqlParameter("@idChainValue", SqlDbType.SmallInt) { Value = _carrierMesAppChainId };
            parms.Add(idChainValue);

            var vcLine = new SqlParameter("@vcLine", _carrierMesAppLine);
            parms.Add(vcLine);

            var vcCredencial = new SqlParameter("@vcCredencial", _operator);
            parms.Add(vcCredencial);

            var admUser = new SqlParameter("@SysAdmUser_Flag", isAdmin ? 1 : 0);
            parms.Add(admUser);

            var idWorkStation = new SqlParameter("@idWorkStation", SqlDbType.SmallInt) { Value = _carrierMesAppWorkStationId };
            parms.Add(idWorkStation);

            var idApplication = new SqlParameter("@idApplication", SqlDbType.SmallInt) { Value = 0 };
            parms.Add(idApplication);

            //outputs
            var flagSts = new SqlParameter("@FlagSts", SqlDbType.SmallInt);
            flagSts.Direction = ParameterDirection.Output;
            parms.Add(flagSts);

            var errorMessage = new SqlParameter("@ErrorMessage", SqlDbType.VarChar, -1);
            errorMessage.Direction = ParameterDirection.Output;
            parms.Add(errorMessage);

            var cascadeFlag = new SqlParameter("@CascadeFlag", SqlDbType.Bit);
            cascadeFlag.Direction = ParameterDirection.Output;
            parms.Add(cascadeFlag);

            var iSuccCode = new SqlParameter("@iSucc_Code", SqlDbType.SmallInt);
            iSuccCode.Direction = ParameterDirection.Output;
            parms.Add(iSuccCode);

            var iFailedCode = new SqlParameter("@iFailed_Code", SqlDbType.SmallInt);
            iFailedCode.Direction = ParameterDirection.Output;
            parms.Add(iFailedCode);

            var res = ExecuteStoredProcedure("spMES_Params_OperatorValid", parms.ToArray());

            return res;
        }

      

        private SqlParameterCollection ExecuteSP_MasterKey_CascadeWSValid(
            bool bCascadeFlag,
            string masterKeyScan,
            short previousSuccessCode
        )
        {
            var parms = new List<SqlParameter>();

            var idChainValue = new SqlParameter("@idChainValue", SqlDbType.SmallInt) { Value = _carrierMesAppChainId };
            parms.Add(idChainValue);

            var vcLine = new SqlParameter("@vcLine", _carrierMesAppLine);
            parms.Add(vcLine);

            var idApplication = new SqlParameter("@idApplication", SqlDbType.SmallInt) { Value = 0 };
            parms.Add(idApplication);

            var idWorkStation = new SqlParameter("@idWorkStation", SqlDbType.SmallInt) { Value = _carrierMesAppWorkStationId };
            parms.Add(idWorkStation);

            //these are command specific
            var cascadeFlag = new SqlParameter("@CascadeFlag", SqlDbType.Bit) { Value = bCascadeFlag };
            parms.Add(cascadeFlag);

            var vcBarcode = new SqlParameter("@vcBarcode", masterKeyScan);
            parms.Add(vcBarcode);

            var succCode = new SqlParameter("@SuccCode", masterKeyScan) { Value = previousSuccessCode };
            parms.Add(succCode);

            //outputs
            var vcMaterial = new SqlParameter("@vcMaterial", SqlDbType.VarChar, 30);
            vcMaterial.Direction = ParameterDirection.Output;
            parms.Add(vcMaterial);

            var flagSts = new SqlParameter("@FlagSts", SqlDbType.SmallInt);
            flagSts.Direction = ParameterDirection.Output;
            parms.Add(flagSts);

            var chargingFlag = new SqlParameter("@ChargingFlag", SqlDbType.SmallInt);
            chargingFlag.Direction = ParameterDirection.Output;
            parms.Add(chargingFlag);

            var errorMessage = new SqlParameter("@ErrorMessage", SqlDbType.VarChar, -1);
            errorMessage.Direction = ParameterDirection.Output;
            parms.Add(errorMessage);


            var res = ExecuteStoredProcedure("spMES_MasterKey_CascadeWSValid", parms.ToArray());

            return res;
        }

        private SqlParameterCollection ExecuteSP_Charging_Params(
            string barcode
        )
        {
            var parms = new List<SqlParameter>();

            var idChainValue = new SqlParameter("@idChainValue", SqlDbType.SmallInt) { Value = _carrierMesAppChainId };
            parms.Add(idChainValue);

            //these are command specific
            var vcBarcode = new SqlParameter("@vcBarcode", barcode);
            parms.Add(vcBarcode);

            //outputs
            var flagSts = new SqlParameter("@FlagSts", SqlDbType.SmallInt);
            flagSts.Direction = ParameterDirection.Output;
            parms.Add(flagSts);

            var errorMessage = new SqlParameter("@ErrorMessage", SqlDbType.VarChar, -1);
            errorMessage.Direction = ParameterDirection.Output;
            parms.Add(errorMessage);

            var vcRefType = new SqlParameter("@vcRefType", SqlDbType.VarChar, 30);
            vcRefType.Direction = ParameterDirection.Output;
            parms.Add(vcRefType);

            var nRefWeight = new SqlParameter("@nRef_Weight", SqlDbType.VarChar, 33);//kg
            nRefWeight.Direction = ParameterDirection.Output;
            parms.Add(nRefWeight);

            var nRefWeight2 = new SqlParameter("@nRef_Weight2", SqlDbType.VarChar, 33);//kg
            nRefWeight2.Direction = ParameterDirection.Output;
            parms.Add(nRefWeight2);

            var nRefWeight3 = new SqlParameter("@nRef_Weight3", SqlDbType.VarChar, 33);//kg
            nRefWeight3.Direction = ParameterDirection.Output;
            parms.Add(nRefWeight3);

            var res = ExecuteStoredProcedure("spMES_Charging_Params_CMX_B", parms.ToArray());

            return res;
        }

        private SqlParameterCollection ExecuteSP_LCV_Charging_Params(
            string barcode
        )
        {
            var parms = new List<SqlParameter>();

            var idChainValue = new SqlParameter("@idChainValue", SqlDbType.SmallInt) { Value = _carrierMesAppChainId };
            parms.Add(idChainValue);

            var vcLine = new SqlParameter("@vcLine", _carrierMesAppLine);
            parms.Add(vcLine);

            //these are command specific
            var vcBarcode = new SqlParameter("@vcBarcode", barcode);
            parms.Add(vcBarcode);

            //outputs
            var flagSts = new SqlParameter("@FlagSts", SqlDbType.SmallInt);
            flagSts.Direction = ParameterDirection.Output;
            parms.Add(flagSts);

            var errorMessage = new SqlParameter("@ErrorMessage", SqlDbType.VarChar, -1);
            errorMessage.Direction = ParameterDirection.Output;
            parms.Add(errorMessage);

            var vcRefType = new SqlParameter("@vcRefType", SqlDbType.VarChar, 30);
            vcRefType.Direction = ParameterDirection.Output;
            parms.Add(vcRefType);

            var nRefChargeKg = new SqlParameter("@nRefCharge_KG", SqlDbType.Decimal);//kg
            nRefChargeKg.Direction = ParameterDirection.Output;
            nRefChargeKg.Precision = 9;
            nRefChargeKg.Scale = 2;
            parms.Add(nRefChargeKg);

            var bDoubleSidesUnitFlag = new SqlParameter("@bDoubleSides_UnitFlag", SqlDbType.Bit);
            bDoubleSidesUnitFlag.Direction = ParameterDirection.Output;
            parms.Add(bDoubleSidesUnitFlag);

            var res = ExecuteStoredProcedure("spMES_LCV_Charging_Params", parms.ToArray());

            return res;
        }

        private SqlParameterCollection ExecuteSP_LCV_SecondCharging_Params(
            string barcode
        )
        {
            var parms = new List<SqlParameter>();

            var idChainValue = new SqlParameter("@idChainValue", SqlDbType.SmallInt) { Value = _carrierMesAppChainId };
            parms.Add(idChainValue);

            var vcLine = new SqlParameter("@vcLine", _carrierMesAppLine);
            parms.Add(vcLine);

            //these are command specific
            var vcBarcode = new SqlParameter("@vcBarcode", barcode);
            parms.Add(vcBarcode);

            //outputs
            var flagSts = new SqlParameter("@FlagSts", SqlDbType.SmallInt);
            flagSts.Direction = ParameterDirection.Output;
            parms.Add(flagSts);

            var errorMessage = new SqlParameter("@ErrorMessage", SqlDbType.VarChar, -1);
            errorMessage.Direction = ParameterDirection.Output;
            parms.Add(errorMessage);

            var vcRefType = new SqlParameter("@vcRefType", SqlDbType.VarChar, 30);
            vcRefType.Direction = ParameterDirection.Output;
            parms.Add(vcRefType);

            var nRemainRefChargeKg = new SqlParameter("@nRemainRefCharge_KG", SqlDbType.Decimal);//kg
            nRemainRefChargeKg.Direction = ParameterDirection.Output;
            nRemainRefChargeKg.Precision = 9;
            nRemainRefChargeKg.Scale = 2;
            parms.Add(nRemainRefChargeKg);

            var bDoubleSidesUnitFlag = new SqlParameter("@bDoubleSides_UnitFlag", SqlDbType.Bit);
            bDoubleSidesUnitFlag.Direction = ParameterDirection.Output;
            parms.Add(bDoubleSidesUnitFlag);

            var bRemainRefChargeUnit = new SqlParameter("@bRemainRefCharge_Unit", SqlDbType.Bit);
            bRemainRefChargeUnit.Direction = ParameterDirection.Output;
            parms.Add(bRemainRefChargeUnit);

            var res = ExecuteStoredProcedure("spMES_LCV_SecondCharging_Params", parms.ToArray());

            return res;
        }

        /// <summary>
        /// Send on circuit complete.
        /// </summary>
        /// <param name="barcode">Scan</param>
        /// <param name="successFailCode">Value(s) come from spMES_Parms_OperatorValid parameters iSucc_Code & iFailed_Code</param>
        /// <param name="material">Value comes from the spMES_MasterKey_CascadeWSValid parameter vcMaterial</param>
        /// <param name="operatorNumber"></param>
        /// <returns></returns>
        private SqlParameterCollection ExecuteSP_SaveResults(
            string barcode,
            int successFailCode,
            string material,
            string operatorNumber
        )
        {
            var parms = new List<SqlParameter>();

            var idChainValue = new SqlParameter("@idChainValue", SqlDbType.SmallInt) { Value = _carrierMesAppChainId };
            parms.Add(idChainValue);

            var vcLine = new SqlParameter("@vcLine", _carrierMesAppLine);
            parms.Add(vcLine);

            var idWorkStation = new SqlParameter("@idWorkStation", SqlDbType.SmallInt) { Value = _carrierMesAppWorkStationId };
            parms.Add(idWorkStation);

            //these are command specific
            var vcBarcode = new SqlParameter("@vcBarcode", barcode);
            parms.Add(vcBarcode);

            var vcMaterial = new SqlParameter("@vcMaterial", material);
            parms.Add(vcMaterial);

            var iSuccFailCode = new SqlParameter("@iSuccFail_Code", successFailCode);
            parms.Add(iSuccFailCode);

            var vcBcNumOperator = new SqlParameter("@vcBCNum_Operator", operatorNumber);
            parms.Add(vcBcNumOperator);

            //outputs
            var flagSts = new SqlParameter("@FlagSts", SqlDbType.SmallInt);
            flagSts.Direction = ParameterDirection.Output;
            parms.Add(flagSts);

            var errorMessage = new SqlParameter("@ErrorMessage", SqlDbType.VarChar, -1);
            errorMessage.Direction = ParameterDirection.Output;
            parms.Add(errorMessage);

            var res = ExecuteStoredProcedure("spMES_SaveResults", parms.ToArray());

            return res;
        }

        //=======
        //MX A Commands
        private SqlParameterCollection ExecuteSP_MXA_ValidateMK_Get(
            string masterKeyScan
        )
        {
            var parms = new List<SqlParameter>();

            var idChainValue = new SqlParameter("@IdChainValue", SqlDbType.VarChar, 50) { Value = _carrierMesAppChainId };
            parms.Add(idChainValue);

            var vcLine = new SqlParameter("@IdLine", SqlDbType.VarChar, 50) { Value = _carrierMesAppLine };
            parms.Add(vcLine);

            var idWorkStation = new SqlParameter("@DeviceID", SqlDbType.VarChar, 50) { Value = _carrierMesAppWorkStationId };
            parms.Add(idWorkStation);

            //these are command specific
            var vcBarcode = new SqlParameter("@Barcode", SqlDbType.VarChar, 50) { Value = masterKeyScan };
            parms.Add(vcBarcode);


            //outputs

            var flagSts = new SqlParameter("@FlagSts", SqlDbType.SmallInt);
            flagSts.Direction = ParameterDirection.Output;
            parms.Add(flagSts);

            var errorMessage = new SqlParameter("@ErrorMessage", SqlDbType.VarChar, -1);
            errorMessage.Direction = ParameterDirection.Output;
            parms.Add(errorMessage);


            var res = ExecuteStoredProcedure("spValidateMK_Get", parms.ToArray());

            return res;
        }

        private SqlParameterCollection ExecuteSP_MXA_RecetaMK_Get(
            string barcode
        )
        {
            var parms = new List<SqlParameter>();

            var vcLine = new SqlParameter("@IdLine", SqlDbType.VarChar, 50) { Value = _carrierMesAppLine };
            parms.Add(vcLine);

            var idWorkStation = new SqlParameter("@DeviceID", SqlDbType.VarChar, 50) { Value = _carrierMesAppWorkStationId };
            parms.Add(idWorkStation);

            //these are command specific
            var vcBarcode = new SqlParameter("@Barcode", SqlDbType.VarChar, 50) { Value = barcode };
            parms.Add(vcBarcode);

            var Limit_Type_ID = new SqlParameter("@Limit_Type_ID", SqlDbType.VarChar, 50) { Value = _carrierMesLimitTypeID };
            parms.Add(vcBarcode);

            //outputs
            var flagSts = new SqlParameter("@FlagSts", SqlDbType.SmallInt);
            flagSts.Direction = ParameterDirection.Output;
            parms.Add(flagSts);

            var errorMessage = new SqlParameter("@ErrorMessage", SqlDbType.VarChar, -1);
            errorMessage.Direction = ParameterDirection.Output;
            parms.Add(errorMessage);

            var EvacL = new SqlParameter("@EvacL", SqlDbType.Float);
            EvacL.Direction = ParameterDirection.Output;
            parms.Add(EvacL);

            var EvacT = new SqlParameter("@EvacT", SqlDbType.Float);
            EvacT.Direction = ParameterDirection.Output;
            parms.Add(EvacT);

            var VacuumL = new SqlParameter("@VacuumL", SqlDbType.Float);
            VacuumL.Direction = ParameterDirection.Output;
            parms.Add(VacuumL);

            var VacuumT = new SqlParameter("@VacuumT", SqlDbType.Float);
            VacuumT.Direction = ParameterDirection.Output;
            parms.Add(VacuumT);

            var ChargeQty = new SqlParameter("@ChargeQty", SqlDbType.Float);
            ChargeQty.Direction = ParameterDirection.Output;
            parms.Add(ChargeQty);

            var Coolant = new SqlParameter("@Coolant", SqlDbType.Float);
            Coolant.Direction = ParameterDirection.Output;
            parms.Add(Coolant);

            var Percent = new SqlParameter("@Percent", SqlDbType.Float);
            Percent.Direction = ParameterDirection.Output;
            parms.Add(Percent);

            var res = ExecuteStoredProcedure("spMES_Charging_Params_CMX_B", parms.ToArray());

            return res;
        }


        private SqlParameterCollection ExecuteSP_MXA_SaveResults(
            string barcode,
            int successFailCode
        )
        {
            var parms = new List<SqlParameter>();

            var idChainValue = new SqlParameter("@idChainValue", SqlDbType.VarChar, 100) { Value = _carrierMesAppChainId.ToString() };
            parms.Add(idChainValue);

            var vcLine = new SqlParameter("@IdLine", SqlDbType.VarChar, 100) { Value = _carrierMesAppLine };
            parms.Add(vcLine);

            var idWorkStation = new SqlParameter("@DeviceID", SqlDbType.VarChar, 100) { Value = _carrierMesAppWorkStationId };
            parms.Add(idWorkStation);

            //these are command specific
            var vcBarcode = new SqlParameter("@Barcode", SqlDbType.VarChar, 100) { Value = barcode };
            parms.Add(vcBarcode);

            var iSuccFailCode = new SqlParameter("@IDFailCode", successFailCode);//0 = failed, 1 = passed, 2 = manual passed (bypass)
            parms.Add(iSuccFailCode);

            var Limit_Type_ID = new SqlParameter("@LIMIT_TYPE_ID", SqlDbType.VarChar, 100) { Value = _carrierMesLimitTypeID };
            parms.Add(vcBarcode);

            //outputs
            var flagSts = new SqlParameter("@FlagSts", SqlDbType.SmallInt);
            flagSts.Direction = ParameterDirection.Output;
            parms.Add(flagSts);

            var errorMessage = new SqlParameter("@ErrorMessage", SqlDbType.VarChar, -1);
            errorMessage.Direction = ParameterDirection.Output;
            parms.Add(errorMessage);

            var res = ExecuteStoredProcedure("spCargadoraResultHeader_SET", parms.ToArray());

            return res;
        }


        //End MX A Commands
        //=======


        private bool ValidateSqlResult(
            SqlParameterCollection spResult
        )
        {
            if (spResult.Contains("@FlagSts"))
            {
                if (Convert.ToInt16(spResult["@FlagSts"].Value) == 1)
                {
                    return true;
                }
                SetError(51, $"Stored Procedure Failure - {spResult["@ErrorMessage"].Value}");
                return false;
            }

            SetError(50, "@FlagSts not in Query Result");
            return false;
        }

        private bool SqlResultIntoRecipe(
            SqlParameterCollection spResult,
            ref Recipe recipe
        )
        {
            try
            {
                foreach (SqlParameter param in spResult)
                {
                    SetRecipeItem(ref recipe, param.ParameterName, param.Value);
                }
            }
            catch (Exception ex)
            {
                AddLogItem("Carrier-MX-MES - Error Updating RecipeItem from SqlParameter(s).", ex);
                return false;
            }

            return true;
        }

        private void UpdateAppConfig()
        {
            try
            {
                using (var appXml = new DataSet())
                {
                    appXml.ReadXml(_carrierMesAppConfigPath);
                    if (appXml.Tables["AppConfig"] != null)
                    {
                        var appConfig = appXml.Tables["AppConfig"];
                        if (appConfig.Columns.Contains("IdChainValue") && appConfig.Columns.Contains("vcLine") && appConfig.Columns.Contains("WorkStation_ID"))
                        {
                            if (Convert.IsDBNull(appConfig.Rows[0]["IdChainValue"]) ||
                                !int.TryParse(appConfig.Rows[0]["IdChainValue"]
                                                       .ToString(),
                                              out _carrierMesAppChainId
                                ))
                            {
                                SetError(53, $"App.config.xml ChainValue is incorrect.");
                            }

                            if (!Convert.IsDBNull(appConfig.Rows[0]["vcLine"]))
                            {
                                _carrierMesAppLine = appConfig.Rows[0]["vcLine"]
                                                              .ToString();
                            }
                            else SetError(53, $"App.config.xml ChainValue is incorrect.");

                            if (Convert.IsDBNull(appConfig.Rows[0]["WorkStation_ID"]) ||
                                !int.TryParse(appConfig.Rows[0]["WorkStation_ID"]
                                                       .ToString(),
                                              out _carrierMesAppWorkStationId
                                ))
                            {
                                SetError(53, $"App.config.xml WorkStation_ID is incorrect.");
                            }

                        }
                        //jrg 2023-10-12 - MX A decided on different names...
                        if (appConfig.Columns.Contains("IdLine") && !Convert.IsDBNull(appConfig.Rows[0]["IdLine"]))
                        {

                            _carrierMesAppLine = appConfig.Rows[0]["IdLine"].ToString();

                        }
                        //for MX A this may not always be an int? We would need to do the convert @ sp call time if that's the case...
                        if (appConfig.Columns.Contains("DeviceID") && !Convert.IsDBNull(appConfig.Rows[0]["DeviceID"]))
                        {
                            if (!int.TryParse(appConfig.Rows[0]["DeviceID"]
                                                       .ToString(),
                                              out _carrierMesAppWorkStationId
                                ))
                            {
                                SetError(53, $"App.config.xml WorkStation_ID is incorrect.");
                            }
                        }
                        //jrg 2023-10-12 - Added for MX A, will only show up for them
                        if (appConfig.Columns.Contains("Limit_Type_ID") && !Convert.IsDBNull(appConfig.Rows[0]["Limit_Type_ID"]))
                        {

                            _carrierMesLimitTypeID = appConfig.Rows[0]["Limit_Type_ID"].ToString();

                        }
                    }
                }
            }
            catch (Exception ex)
            {

                SetError(52, $"Exception Parsing App.config");
            }

            if (_debug) AddLogItem($"Carrier-MX-MES - App Config Loaded: ChainID: {_carrierMesAppChainId}; vcLine: {_carrierMesAppLine}; WorkStation_ID: {_carrierMesAppWorkStationId}");

        }
    }
}
