using Dapper;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json.Linq;
using OI.Metrology.Server.Models;
using OI.Metrology.Shared.DataModels;
using OI.Metrology.Shared.Models.Stateless;
using OI.Metrology.Shared.Repositories;
using System.Data;
using System.Data.Common;
using System.Text;
using System.Transactions;

#pragma warning disable CS8600, CS8602, CS8603, CS8604, CS8625

namespace OI.Metrology.Server.Repositories;

public class MetrologyRepository : IMetrologyRepository
{
    private readonly string _MockRoot;
    private readonly IMemoryCache _Cache;
    private readonly IDbConnectionFactory _DBConnectionFactory;

    public MetrologyRepository(AppSettings appSettings, IDbConnectionFactory dbConnectionFactory, IMemoryCache memoryCache)
    {
        _Cache = memoryCache;
        _MockRoot = appSettings.MockRoot;
        _DBConnectionFactory = dbConnectionFactory;
    }

    private DbConnection GetDbConnection() => _DBConnectionFactory.GetDbConnection();

    protected DbProviderFactory GetDbProviderFactory(IDbConnection conn) =>
        DbProviderFactories.GetFactory(conn.GetType().Namespace);

    internal static TransactionScope StartTransaction() => new();

    protected void CacheItem(string key, object v)
    {
        System.Diagnostics.Debug.WriteLine("CacheItem: " + key);

        _ = _Cache.Set(key, v, new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromHours(1)));
    }

    internal IEnumerable<ToolType> GetToolTypes()
    {
        IEnumerable<ToolType> cached;
        string cacheKey = "GetToolTypes";
        if (_Cache.TryGetValue(cacheKey, out cached))
            return cached;

        using DbConnection conn = GetDbConnection();
        IEnumerable<ToolType> r = conn.Query<ToolType>("SELECT * FROM ToolType");

        CacheItem(cacheKey, r);

        return r;
    }

    internal ToolType GetToolTypeByName(string name)
    {
        ToolType cached;
        string cacheKey = "GetToolTypeByName_" + name;
        if (_Cache.TryGetValue(cacheKey, out cached))
            return cached;

        using DbConnection conn = GetDbConnection();
        ToolType r = conn.QueryFirstOrDefault<ToolType>(
            "SELECT * FROM ToolType WHERE ToolTypeName = @name",
            new { name });

        CacheItem(cacheKey, r);

        return r;
    }

    internal ToolType GetToolTypeByID(int id)
    {
        ToolType cached;
        string cacheKey = "GetToolTypeByID_" + id.ToString();
        if (_Cache.TryGetValue(cacheKey, out cached))
            return cached;

        using DbConnection conn = GetDbConnection();
        ToolType r = conn.QueryFirstOrDefault<ToolType>(
            "SELECT * FROM ToolType WHERE ID = @id",
            new { id });

        CacheItem(cacheKey, r);

        return r;
    }

    internal IEnumerable<ToolTypeMetadata> GetToolTypeMetadataByToolTypeID(int id)
    {
        IEnumerable<ToolTypeMetadata> cached;
        string cacheKey = "GetToolTypeMetadataByToolTypeID_" + id.ToString();
        if (_Cache.TryGetValue(cacheKey, out cached))
            return cached;

        using DbConnection conn = GetDbConnection();
        IEnumerable<ToolTypeMetadata> r = conn.Query<ToolTypeMetadata>(
            "SELECT * FROM ToolTypeMetadata WHERE ToolTypeID = @id",
            new { id });

        CacheItem(cacheKey, r);

        return r;
    }

    internal long InsertToolDataJSON(JToken jsonrow, long headerId, List<ToolTypeMetadata> metaData, string tableName)
    {
        long r = -1;
        using (DbConnection conn = GetDbConnection())
        {
            bool isHeader = headerId <= 0;

            // get fields from metadata
            List<ToolTypeMetadata> fields = metaData.Where(md => md.Header == isHeader).ToList();

            // maps ApiName to ColumnName
            Dictionary<string, string> fieldmap = new();

            // store property name of container field
            string containerField = null;

            // maps container ApiName to ColumnName
            Dictionary<string, string> containerFieldMap = new();

            // build field map
            foreach (ToolTypeMetadata f in fields)
            {
                if ((f.ApiName is not null) && f.ApiName.Contains('\\'))
                {
                    string n = f.ApiName.Split('\\')[0].Trim().ToUpper();
                    if (containerField is null)
                        containerField = n;
                    else if (!string.Equals(containerField, n))
                        throw new Exception("Only one container field is allowed");

                    string pn = f.ApiName.Split('\\')[1].Trim().ToUpper();
                    containerFieldMap.Add(pn, f.ColumnName.Trim());
                }
                else if (!string.IsNullOrWhiteSpace(f.ApiName) && !string.IsNullOrWhiteSpace(f.ColumnName))
                {
                    fieldmap.Add(f.ApiName.Trim().ToUpper(), f.ColumnName.Trim());
                }
            }

            if (containerField is null)
            {
                // No container field, just  insert a single row

                r = InsertRowFromJSON(conn, tableName, jsonrow, fieldmap, headerId, null, null);
            }
            else
            {
                // Find the container field in the json
                JProperty contJP = jsonrow.Children<JProperty>().Where(c => string.Equals(c.Name.Trim(), containerField, StringComparison.OrdinalIgnoreCase)).SingleOrDefault();

                if ((contJP is not null) && (contJP.Value is JArray array))
                {
                    JArray contRows = array;

                    // Insert a row for each row in the container field
                    foreach (JToken contRow in contRows)
                    {
                        r = InsertRowFromJSON(conn, tableName, jsonrow, fieldmap, headerId, contRow, containerFieldMap);
                    }
                }
                else
                    throw new Exception("Invalid container field type");
            }
        }
        return r;
    }

    protected void AddParameter(IDbCommand cmd, string name, object value)
    {
        IDbDataParameter p = cmd.CreateParameter();
        p.ParameterName = name;
        p.Value = value;
        _ = cmd.Parameters.Add(p);
    }

    protected long InsertRowFromJSON(
        IDbConnection conn,
        string tableName,
        JToken jsonrow,
        Dictionary<string, string> fieldmap,
        long headerId,
        JToken containerrow,
        Dictionary<string, string> containerFieldmap)
    {
        // Translate the json into a SQL INSERT using the field map

        IDbCommand cmd = conn.CreateCommand();

        string columns = "INSERT INTO [" + tableName + "](";
        string parms = ") SELECT ";
        int parmnumber = 1;

        if (headerId > 0)
        {
            columns += "HeaderID,";
            parms += "@HeaderID,";
            AddParameter(cmd, "@HeaderID", headerId);
        }

        foreach (JProperty jp in jsonrow.Children<JProperty>())
        {
            string apifield = jp.Name.Trim().ToUpper();

            if (fieldmap.TryGetValue(apifield, out string value))
            {
                string parmname = string.Format("@p{0}", parmnumber);

                columns += string.Format("[{0}],", value);
                parms += parmname;
                parms += ",";
                parmnumber += 1;

                object sqlValue = ((JValue)jp.Value).Value;
                sqlValue ??= DBNull.Value;

                AddParameter(cmd, parmname, sqlValue);
            }
        }

        if ((containerrow is not null) && (containerFieldmap is not null))
        {

            foreach (JProperty jp in containerrow.Children<JProperty>())
            {
                string apifield = jp.Name.Trim().ToUpper();

                if (containerFieldmap.TryGetValue(apifield, out string value))
                {
                    string parmname = string.Format("@p{0}", parmnumber);

                    columns += string.Format("[{0}],", value);
                    parms += parmname;
                    parms += ",";
                    parmnumber += 1;

                    object sqlValue = ((JValue)jp.Value).Value;
                    sqlValue ??= DBNull.Value;

                    AddParameter(cmd, parmname, sqlValue);
                }
            }

        }

        if (parmnumber == 1)
            throw new Exception("JSON had no fields");

        cmd.CommandText = columns.TrimEnd(',') + parms.TrimEnd(',') + ";SELECT SCOPE_IDENTITY();";

        object o = cmd.ExecuteScalar();
        if ((o is null) || Convert.IsDBNull(o))
            throw new Exception("Unexpected query result");
        return Convert.ToInt64(o);
    }

    internal DataTable ExportData(string spName, DateTime startTime, DateTime endTime)
    {
        DataTable dt = new();
        DateTime endTimeLocal = endTime.ToLocalTime();
        DateTime startTimeLocal = startTime.ToLocalTime();
        using (DbConnection conn = GetDbConnection())
        {
            DbProviderFactory factory = GetDbProviderFactory(conn);

            DbCommand cmd = factory.CreateCommand();
            cmd.Connection = conn;
            cmd.CommandText = spName;
            cmd.CommandType = CommandType.StoredProcedure;
            AddParameter(cmd, "@StartTime", startTimeLocal);
            AddParameter(cmd, "@EndTime", endTimeLocal);

            DbDataAdapter da = factory.CreateDataAdapter();
            da.SelectCommand = cmd;

            _ = da.Fill(dt);
        }
        return dt;
    }

    protected string FormDynamicSelectQuery(IEnumerable<ToolTypeMetadata> fields, string tableName)
    {
        StringBuilder sb = new();
        _ = sb.Append("SELECT ");
        bool firstField = true;
        foreach (ToolTypeMetadata f in fields)
        {
            if (!string.IsNullOrWhiteSpace(f.ColumnName))
            {
                if (!firstField)
                    _ = sb.Append(',');
                if (f.GridAttributes is not null && f.GridAttributes.Contains("isNull"))
                {
                    _ = sb.AppendFormat("{0}", "ISNULL(" + f.ColumnName + ", '')[" + f.ColumnName + "]");
                }
                else
                {
                    _ = sb.AppendFormat("[{0}]", f.ColumnName);
                }
                firstField = false;
            }
        }
        _ = sb.AppendFormat(" FROM [{0}] ", tableName);
        return sb.ToString();
    }

    internal DataTable GetHeaders(int toolTypeId, DateTime? startTime, DateTime? endTime, int? pageNo, int? pageSize, long? headerId, out long totalRecords)
    {
        ToolType tt = GetToolTypeByID(toolTypeId);
        if (tt is null)
            throw new Exception("Invalid tool type ID");

        IEnumerable<ToolTypeMetadata> md = GetToolTypeMetadataByToolTypeID(toolTypeId);
        if (md is null)
            throw new Exception("Invalid tool type metadata");

        DataTable dt = new();
        using (DbConnection conn = GetDbConnection())
        {
            StringBuilder sb = new();
            _ = sb.Append(
                FormDynamicSelectQuery(
                    md.Where(m => m.Header == true).ToList(),
                    tt.HeaderTableName)
            );

            DbProviderFactory factory = GetDbProviderFactory(conn);

            DbCommand cmd = factory.CreateCommand();

            string whereClause = "";

            if (headerId.HasValue && headerId.Value > 0)
            {
                whereClause = "ID = @HeaderID ";
                AddParameter(cmd, "@HeaderID", headerId.Value);
            }
            else
            {
                if (startTime.HasValue)
                {
                    whereClause = "[Date] >= @StartTime ";
                    DateTime startTimeLocal = startTime.Value.ToLocalTime();
                    AddParameter(cmd, "@StartTime", startTimeLocal);
                }

                if (endTime.HasValue)
                {
                    DateTime endTimeLocal = endTime.Value.ToLocalTime();
                    TimeSpan timeSpan = new(DateTime.Now.Ticks - endTimeLocal.Ticks);
                    if (timeSpan.TotalMinutes > 5)
                    {
                        if (whereClause.Length > 0)
                            whereClause += "AND ";
                        whereClause += "[Date] <= @EndTime ";

                        AddParameter(cmd, "@EndTime", endTimeLocal);
                    }
                }
            }

            if (whereClause.Length > 0)
            {
                _ = sb.Append("WHERE ");
                _ = sb.Append(whereClause);
            }

            if (pageNo.HasValue && pageSize.HasValue)
            {
                _ = sb.Append("ORDER BY [Date] DESC OFFSET @PageNum * @PageSize ROWS FETCH NEXT @PageSize ROWS ONLY");

                AddParameter(cmd, "@PageNum", pageNo.Value);
                AddParameter(cmd, "@PageSize", pageSize.Value);
            }
            else
            {
                _ = sb.Append("ORDER BY [Date] DESC");
            }

            cmd.Connection = conn;
            cmd.CommandText = sb.ToString();
            cmd.CommandType = CommandType.Text;

            DbDataAdapter da = factory.CreateDataAdapter();
            da.SelectCommand = cmd;

            _ = da.Fill(dt);

            cmd.CommandText = "SELECT COUNT(*) FROM [" + tt.HeaderTableName + "] ";
            if (whereClause.Length > 0)
            {
                cmd.CommandText += "WHERE ";
                cmd.CommandText += whereClause;
            }
            totalRecords = Convert.ToInt64(cmd.ExecuteScalar());
        }
        return dt;
    }

    internal DataTable GetData(int toolTypeId, long headerid)
    {
        ToolType tt = GetToolTypeByID(toolTypeId);
        if (tt is null)
            throw new Exception("Invalid tool type ID");

        IEnumerable<ToolTypeMetadata> md = GetToolTypeMetadataByToolTypeID(toolTypeId);
        if (md is null)
            throw new Exception("Invalid tool type metadata");

        DataTable dt = new();
        using (DbConnection conn = GetDbConnection())
        {
            StringBuilder sb = new();
            _ = sb.Append(
                FormDynamicSelectQuery(
                    md.Where(m => m.Header == false).OrderBy(m => m.GridDisplayOrder).ToList(),
                    tt.DataTableName)
            );

            DbProviderFactory factory = GetDbProviderFactory(conn);

            DbCommand cmd = factory.CreateCommand();

            _ = sb.Append("WHERE [HeaderID] = @HeaderID ");

            if (!string.IsNullOrWhiteSpace(tt.DataGridSortBy))
            {
                _ = sb.AppendFormat("ORDER BY {0} ", tt.DataGridSortBy);
            }

            AddParameter(cmd, "@HeaderID", headerid);

            cmd.Connection = conn;
            cmd.CommandText = sb.ToString();
            cmd.CommandType = CommandType.Text;

            DbDataAdapter da = factory.CreateDataAdapter();
            da.SelectCommand = cmd;

            _ = da.Fill(dt);
        }

        // this code will add a couple of rows with stats calculations
        if (!string.IsNullOrWhiteSpace(tt.DataGridStatsColumn))
        {
            if (dt.Columns.Contains(tt.DataGridStatsColumn))
            {
                double sumAll = 0;
                double sumAllQ = 0;

                foreach (DataRow dr in dt.Rows)
                {
                    try
                    {
                        object v = dr[tt.DataGridStatsColumn];
                        if (!Convert.IsDBNull(v))
                        {
                            double d = Convert.ToDouble(v);
                            sumAll += d;
                            sumAllQ += d * d;
                        }
                    }
                    catch
                    {
                    }
                }

                double length = Convert.ToDouble(dt.Rows.Count);
                double meanAverage = Math.Round(sumAll / length, 4);
                double stdDev = Math.Sqrt((sumAllQ - sumAll * sumAll / length) * (1.0d / (length - 1)));
                string stdDevStr = "";
                if (tt.DataGridStatsStdDevType == "%")
                    stdDevStr = Math.Round(stdDev / meanAverage * 100.0d, 2).ToString("0.####") + "%";
                else
                    stdDevStr = Math.Round(stdDev, 4).ToString("0.####");

                int labelIndex = dt.Columns[tt.DataGridStatsColumn].Ordinal - 1;
                DataRow newrow = dt.NewRow();
                newrow["ID"] = -1;
                newrow[labelIndex] = "Average";
                newrow[tt.DataGridStatsColumn] = meanAverage;
                dt.Rows.Add(newrow);

                newrow = dt.NewRow();
                newrow["ID"] = -2;
                newrow[labelIndex] = "Std Dev";
                newrow[tt.DataGridStatsColumn] = stdDevStr;
                dt.Rows.Add(newrow);
            }
        }

        return dt;
    }

    internal Guid GetHeaderAttachmentID(int toolTypeId, long headerId)
    {
        ToolType tt = GetToolTypeByID(toolTypeId);
        if (tt is null)
            throw new Exception("Invalid tool type ID");

        using DbConnection conn = GetDbConnection();
        string sql =
            $"UPDATE [{tt.HeaderTableName}] SET AttachmentID = NEWID() WHERE ID = @HeaderID AND AttachmentID IS NULL; " +
            $"SELECT AttachmentID FROM [{tt.HeaderTableName}] WHERE ID = @HeaderID";
        return conn.ExecuteScalar<Guid>(sql, param: new { HeaderID = headerId });
    }
    internal string GetHeaderInsertDate(int toolTypeId, long headerId)
    {
        ToolType tt = GetToolTypeByID(toolTypeId);
        if (tt is null)
            throw new Exception("Invalid tool type ID");

        using DbConnection conn = GetDbConnection();
        string sql =
            $"SELECT CONVERT(varchar, case when [InsertDate] < [Date] or [Date] is null then [InsertDate] else [Date] end, 120) d FROM[{tt.HeaderTableName}] where ID = @HeaderID";
        return conn.ExecuteScalar<string>(sql, param: new { HeaderID = headerId });
    }
    internal string GetAttachmentInsertDateByGUID(string tableName, Guid attachmentId)
    {
        using DbConnection conn = GetDbConnection();
        string sql = "";
        if (tableName is "SP1RunData" or "TencorRunData")
        {
            sql = $"SELECT [InsertDate] FROM[{tableName}] where AttachmentID = @AttachmentID";
        }
        else
        {
            sql = $"SELECT [InsertDate] FROM[{tableName}] where AttachmentID = @AttachmentID";
        }

        return conn.ExecuteScalar<string>(sql, param: new { AttachmentID = attachmentId });
    }
    internal void SetHeaderDirName(string tableName, long headerId, string dateDir)
    {
        using DbConnection conn = GetDbConnection();
        _ = conn.Execute($"UPDATE [{tableName}] SET AttachDirName = @AttachDirName WHERE ID = @HeaderID;", new { HeaderID = headerId, AttachDirName = dateDir });
    }

    internal Guid GetDataAttachmentID(int toolTypeId, long headerId, string title)
    {
        ToolType tt = GetToolTypeByID(toolTypeId);
        if (tt is null)
            throw new Exception("Invalid tool type ID");

        using DbConnection conn = GetDbConnection();
        string sql =
            $"UPDATE [{tt.DataTableName}] SET AttachmentID = NEWID() WHERE HeaderID = @HeaderID AND Title = @Title AND AttachmentID IS NULL; " +
            $"SELECT AttachmentID FROM [{tt.DataTableName}] WHERE HeaderID = @HeaderID AND Title = @Title";
        return conn.ExecuteScalar<Guid>(sql, param: new { HeaderID = headerId, Title = title });
    }
    // J Ouellette Added
    internal string GetDataInsertDate(int toolTypeId, long headerId, string title)
    {
        ToolType tt = GetToolTypeByID(toolTypeId);
        if (tt is null)
            throw new Exception("Invalid tool type ID");

        using DbConnection conn = GetDbConnection();
        string sql = "";
        if (tt.DataTableName is "" or "")
        {
            sql = $"SELECT InsertDate FROM [{tt.DataTableName}] WHERE HeaderID = @HeaderID AND Title = @Title";
        }
        else
        {
            sql = $"SELECT [InsertDate] FROM[{tt.DataTableName}] WHERE HeaderID = @HeaderID AND Title = @Title";
        }

        return conn.ExecuteScalar<string>(sql, param: new { HeaderID = headerId, Title = title });
    }
    internal void SetDataDirName(string tableName, long headerId, string title, string dateDir)
    {
        using DbConnection conn = GetDbConnection();
        string sql =
            $"UPDATE [{tableName}] SET AttachDirName = @AttachDirName WHERE HeaderID = @HeaderID AND Title = @Title;";
        _ = conn.Execute(sql, param: new { HeaderID = headerId, Title = title, AttachDirName = dateDir });
    }

    internal void PurgeExistingData(int toolTypeId, string title)
    {
        using DbConnection conn = GetDbConnection();
        _ = conn.Execute("PurgeExistingData", param: new { ToolTypeID = toolTypeId, Title = title }, commandType: CommandType.StoredProcedure);
    }

    internal DataSet GetOIExportData(int toolTypeId, long headerid)
    {
        ToolType tt = GetToolTypeByID(toolTypeId);
        if (tt is null)
            throw new Exception("Invalid tool type ID");

        if (string.IsNullOrWhiteSpace(tt.OIExportSPName))
            throw new Exception("OpenInsight export not available for " + tt.ToolTypeName);

        DataSet ds = new();
        using (DbConnection conn = GetDbConnection())
        {
            DbProviderFactory factory = GetDbProviderFactory(conn);

            DbCommand cmd = factory.CreateCommand();

            cmd.Connection = conn;
            cmd.CommandText = tt.OIExportSPName;
            cmd.CommandType = CommandType.StoredProcedure;

            AddParameter(cmd, "@ID", headerid);

            DbDataAdapter da = factory.CreateDataAdapter();
            da.SelectCommand = cmd;
            _ = da.Fill(ds);
        }
        return ds;
    }

    private HeaderCommon[] GetHeaderTitles()
    {
        IEnumerable<HeaderCommon> results;
        ToolType[] toolTypes = GetToolTypes().ToArray();

        if (!toolTypes.Any() || toolTypes.FirstOrDefault() is null)
            throw new Exception("Invalid tool type ID");

        ToolType tt;
        StringBuilder stringBuilder = new();
        using DbConnection conn = GetDbConnection();
        _ = stringBuilder.Append(" SELECT * FROM ( ");
        for (int i = 0; i < toolTypes.Length; i++)
        {
            tt = toolTypes[i];
            _ = stringBuilder.Append($" SELECT ID, InsertDate, AttachmentID, Title, [Date], {tt.ID} AS ToolTypeID, '{tt.ToolTypeName}' AS ToolTypeName, Reactor, RDS, PSN FROM {tt.HeaderTableName} ");
            if (i != toolTypes.Length - 1)
                _ = stringBuilder.Append(" UNION ALL ");
        }
        _ = stringBuilder.Append(" ) AS A ORDER BY A.[Date] DESC ");
        results = conn.Query<HeaderCommon>(stringBuilder.ToString()).ToArray();

        return results.ToArray();
    }

    internal HeaderCommon[] GetHeaderTitles(int? toolTypeId, int? pageNo, int? pageSize, out long totalRecords)
    {
        HeaderCommon[] headers;
        if (toolTypeId is not null && (pageNo is not null || pageSize is not null))
            throw new Exception();

        if (toolTypeId is null)
        {
            headers = GetHeaderTitles();
            totalRecords = headers.Length;
            return headers;
        }

        ToolType tt = GetToolTypeByID(toolTypeId.Value);
        if (tt is null)
            throw new Exception("Invalid tool type ID");

        using DbConnection conn = GetDbConnection();
        string sql = $"SELECT ID, InsertDate, AttachmentID, Title, [Date], {tt.ID} AS ToolTypeID, '{tt.ToolTypeName}' AS ToolTypeName, Reactor, RDS, PSN FROM {tt.HeaderTableName} ORDER BY [Date] DESC ";

        if (pageNo.HasValue && pageSize.HasValue)
        {
            sql += "OFFSET @PageNum * @PageSize ROWS FETCH NEXT @PageSize ROWS ONLY";

            headers = conn.Query<HeaderCommon>(sql, param: new { PageNum = pageNo.Value, PageSize = pageSize.Value }).ToArray();
        }
        else
        {
            headers = conn.Query<HeaderCommon>(sql).ToArray();
        }

        sql = $"SELECT COUNT(*) FROM [{tt.HeaderTableName}] ";

        totalRecords = Convert.ToInt64(conn.ExecuteScalar(sql));

        return headers;
    }

    internal IEnumerable<KeyValuePair<string, string>> GetHeaderFields(int toolTypeId, long headerid)
    {
        ToolType tt = GetToolTypeByID(toolTypeId);
        if (tt is null)
            throw new Exception("Invalid tool type ID");

        IEnumerable<ToolTypeMetadata> md = GetToolTypeMetadataByToolTypeID(toolTypeId);
        if (md is null)
            throw new Exception("Invalid tool type metadata");

        List<KeyValuePair<string, string>> r = new();

        using (DbConnection conn = GetDbConnection())
        {
            DbProviderFactory factory = GetDbProviderFactory(conn);

            DbCommand cmd = factory.CreateCommand();

            cmd.Connection = conn;
            cmd.CommandText = $"SELECT * FROM [{tt.HeaderTableName}] WHERE ID = @HeaderID";

            AddParameter(cmd, "@HeaderID", headerid);

            DataTable dt = new();
            DbDataAdapter da = factory.CreateDataAdapter();
            da.SelectCommand = cmd;
            _ = da.Fill(dt);
            DataRow dr = null;
            if (dt.Rows.Count > 0)
                dr = dt.Rows[0];

            foreach (ToolTypeMetadata m in md.Where(m => m.Header == true && m.TableDisplayOrder > 0).OrderBy(m => m.TableDisplayOrder))
            {
                string v = "";
                if (dr is not null)
                {
                    object o = dr[m.ColumnName];
                    if (o is not null && !Convert.IsDBNull(o))
                        v = Convert.ToString(o);
                }
                KeyValuePair<string, string> kvp = new(m.DisplayTitle, v);
                r.Add(kvp);
            }
        }
        return r;
    }

    internal IEnumerable<AwaitingDisposition> GetAwaitingDisposition()
    {
        IEnumerable<AwaitingDisposition>? r;
        if (!string.IsNullOrEmpty(_MockRoot))
        {
            string json = File.ReadAllText(Path.Combine(string.Concat(AppContext.BaseDirectory, _MockRoot), "GetAwaitingDispositionApi.json"));
            r = System.Text.Json.JsonSerializer.Deserialize<IEnumerable<AwaitingDisposition>>(json);
            if (r is null)
                throw new NullReferenceException(nameof(r));
        }
        else
        {
            using DbConnection conn = GetDbConnection();
            r = conn.Query<AwaitingDisposition>("GetAwaitingDispo", commandType: CommandType.StoredProcedure);
        }
        return r;
    }

    internal int UpdateReviewDate(int toolTypeId, long headerId, bool clearDate)
    {
        ToolType tt = GetToolTypeByID(toolTypeId);
        if (tt is null)
            throw new Exception("Invalid tool type ID");

        using DbConnection conn = GetDbConnection();
        if (clearDate)
        {
            // if it's already past the 6 hour window, then it won't show in queue anyway, so need to return value so we can show that
            string sql = $"SELECT DATEDIFF(HH, INSERTDATE, GETDATE()) FROM [{tt.HeaderTableName}] WHERE ID = @HeaderID";
            int hrs = conn.ExecuteScalar<int>(sql, param: new { HeaderId = headerId });
            _ = conn.Execute($"UPDATE [{tt.HeaderTableName}] SET ReviewDate = NULL WHERE ID = @HeaderID", new { HeaderID = headerId });
            return hrs;
        }
        else
        {
            _ = conn.Execute($"UPDATE [{tt.HeaderTableName}] SET ReviewDate = GETDATE() WHERE ID = @HeaderID", new { HeaderID = headerId });
            return 1;
        }
    }

    internal Guid GetHeaderAttachmentIDByTitle(int toolTypeId, string title)
    {
        ToolType tt = GetToolTypeByID(toolTypeId);
        if (tt is null)
            throw new Exception("Invalid tool type ID");

        using DbConnection conn = GetDbConnection();
        string sql =
            $"SELECT TOP 1 AttachmentID FROM [{tt.HeaderTableName}] WHERE Title = @Title ORDER BY InsertDate DESC";
        return conn.ExecuteScalar<Guid>(sql, param: new { Title = title });
    }

    internal Guid GetDataAttachmentIDByTitle(int toolTypeId, string title)
    {
        ToolType tt = GetToolTypeByID(toolTypeId);
        if (tt is null)
            throw new Exception("Invalid tool type ID");

        using DbConnection conn = GetDbConnection();
        string sql =
            $"SELECT TOP 1 AttachmentID FROM [{tt.DataTableName}] WHERE Title = @Title ORDER BY InsertDate DESC";
        return conn.ExecuteScalar<Guid>(sql, param: new { Title = title });
    }

    DataTable IMetrologyRepository.GetDataSharePoint(int toolTypeId, string headerId) => throw new NotImplementedException();

    IEnumerable<ToolType> IMetrologyRepository.GetToolTypes() => GetToolTypes();
    ToolType IMetrologyRepository.GetToolTypeByName(string name) => GetToolTypeByName(name);
    ToolType IMetrologyRepository.GetToolTypeByID(int id) => GetToolTypeByID(id);
    IEnumerable<ToolTypeMetadata> IMetrologyRepository.GetToolTypeMetadataByToolTypeID(int id) => GetToolTypeMetadataByToolTypeID(id);
    TransactionScope IMetrologyRepository.StartTransaction() => StartTransaction();
    void IMetrologyRepository.PurgeExistingData(int toolTypeId, string title) => PurgeExistingData(toolTypeId, title);
    long IMetrologyRepository.InsertToolDataJSON(JToken jsonbody, long headerId, List<ToolTypeMetadata> metaData, string tableName) => InsertToolDataJSON(jsonbody, headerId, metaData, tableName);
    DataTable IMetrologyRepository.ExportData(string spName, DateTime startTime, DateTime endTime) => ExportData(spName, startTime, endTime);
    DataTable IMetrologyRepository.GetHeaders(int toolTypeId, DateTime? startTime, DateTime? endTime, int? pageNo, int? pageSize, long? headerid, out long totalRecords) => GetHeaders(toolTypeId, startTime, endTime, pageNo, pageSize, headerid, out totalRecords);
    DataTable IMetrologyRepository.GetData(int toolTypeId, long headerId) => GetData(toolTypeId, headerId);
    HeaderCommon[] IMetrologyRepository.GetHeaderTitles(int? toolTypeId, int? pageNo, int? pageSize, out long totalRecords) => GetHeaderTitles(toolTypeId, pageNo, pageSize, out totalRecords);
    Guid IMetrologyRepository.GetHeaderAttachmentIDByTitle(int toolTypeId, string title) => GetHeaderAttachmentIDByTitle(toolTypeId, title);
    Guid IMetrologyRepository.GetDataAttachmentIDByTitle(int toolTypeId, string title) => GetDataAttachmentIDByTitle(toolTypeId, title);
    Guid IMetrologyRepository.GetHeaderAttachmentID(int toolTypeId, long headerId) => GetHeaderAttachmentID(toolTypeId, headerId);
    string IMetrologyRepository.GetHeaderInsertDate(int toolTypeId, long headerId) => GetHeaderInsertDate(toolTypeId, headerId);
    string IMetrologyRepository.GetAttachmentInsertDateByGUID(string tableName, Guid attachmentId) => GetAttachmentInsertDateByGUID(tableName, attachmentId);
    void IMetrologyRepository.SetHeaderDirName(string tableName, long headerId, string dateDir) => SetHeaderDirName(tableName, headerId, dateDir);
    Guid IMetrologyRepository.GetDataAttachmentID(int toolTypeId, long headerId, string title) => GetDataAttachmentID(toolTypeId, headerId, title);
    string IMetrologyRepository.GetDataInsertDate(int toolTypeId, long headerId, string title) => GetDataInsertDate(toolTypeId, headerId, title);
    void IMetrologyRepository.SetDataDirName(string tableName, long headerId, string title, string dateDir) => SetDataDirName(tableName, headerId, title, dateDir);
    DataSet IMetrologyRepository.GetOIExportData(int toolTypeId, long headerid) => GetOIExportData(toolTypeId, headerid);
    IEnumerable<KeyValuePair<string, string>> IMetrologyRepository.GetHeaderFields(int toolTypeId, long headerid) => GetHeaderFields(toolTypeId, headerid);
    IEnumerable<AwaitingDisposition> IMetrologyRepository.GetAwaitingDisposition() => GetAwaitingDisposition();
    int IMetrologyRepository.UpdateReviewDate(int toolTypeId, long headerId, bool clearDate) => UpdateReviewDate(toolTypeId, headerId, clearDate);

}