//******************************************************************************************************
//  MeasurementOperations.cs - Gbtc
//
//  Copyright © 2026, Grid Protection Alliance.  All Rights Reserved.
//
//  Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
//  the NOTICE file distributed with this work for additional information regarding copyright ownership.
//  The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
//  file except in compliance with the License. You may obtain a copy of the License at:
//
//      http://opensource.org/licenses/MIT
//
//  Unless agreed to in writing, the subject software distributed under the License is distributed on an
//  "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
//  License for the specific language governing permissions and limitations.
//
//  Code Modification History:
//  ----------------------------------------------------------------------------------------------------
//  01/15/2026 - J. Ritchie Carroll
//       Generated original version of source code.
//
//******************************************************************************************************

using System.Diagnostics;
using Gemstone.Data.Model;
using Gemstone.Numeric.EE;

namespace DNP3ConfigLoader.TableOperations;

/// <summary>
/// Provides operations for Measurement table records.
/// </summary>
public static class MeasurementOperations
{
    /// <summary>
    /// Gets or creates a measurement record by signal reference.
    /// </summary>
    public static Measurement? GetMeasurementRecord(
        TableOperations<Measurement> measurementOps,
        int deviceID,
        int? historianID,
        int signalTypeID,
        string pointTag,
        string alternateTag,
        string signalReference,
        double scale,
        string description,
        Dictionary<string, Measurement>? cache = null)
    {
        try
        {
            Measurement? measurement = null;

            // Check in-memory cache first, then fall back to database query
            cache?.TryGetValue(signalReference, out measurement);
            measurement ??= measurementOps.QueryRecordWhere("SignalReference = {0}", signalReference);

            if (measurement is not null)
            {
                // If the record exists, check if it matches the provided details
                if (measurement.DeviceID == deviceID &&
                    measurement.HistorianID == historianID &&
                    measurement.SignalTypeID == signalTypeID &&
                    measurement.PointTag == pointTag &&
                    measurement.AlternateTag == alternateTag &&
                    Math.Abs(measurement.Multiplier - scale) < 0.0001D &&
                    measurement.Description == description)
                {
                    return measurement;
                }
            }
            else
            {
                measurement = measurementOps.NewRecord();
                Debug.Assert(measurement is not null, "Failed to create new Measurement record.");
            }

            // Update record fields
            measurement.HistorianID = historianID;
            measurement.DeviceID = deviceID;
            measurement.PointTag = pointTag;
            measurement.AlternateTag = alternateTag;
            measurement.SignalReference = signalReference;
            measurement.SignalTypeID = signalTypeID;
            measurement.Multiplier = scale;
            measurement.Adder = 0.0D;
            measurement.Description = description;
            measurement.Internal = true;
            measurement.Subscribed = false;
            measurement.Enabled = true;

            if (measurement.PointID == 0)
            {
                measurementOps.AddNewRecord(measurement);

                // Re-query to get database-assigned ID
                measurement = measurementOps.QueryRecordWhere("SignalReference = {0}", signalReference);
            }
            else
            {
                measurementOps.UpdateRecord(measurement);
            }

            // Update cache with new/modified record
            if (cache is not null && measurement is not null)
                cache[signalReference] = measurement;

            return measurement;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error occurred while reading or creating Measurement record for \"{pointTag}: {description}\": {ex.Message}");
            return null;
        }
    }

    /// <summary>
    /// Removes measurements that are beyond the specified index for a device and signal type.
    /// </summary>
    public static void RemoveUnreferencedMeasurements(
        TableOperations<Measurement> measurementOps,
        int lastIndex,
        int deviceID,
        int signalTypeID,
        string signalType)
    {
        if (lastIndex <= 0)
            return;

        // Query all measurements for this device and signal type
        IEnumerable<Measurement> measurements = measurementOps.QueryRecordsWhere(
            "DeviceID = {0} AND SignalTypeID = {1}", deviceID, signalTypeID)
            .Where(m => m is not null)!;

        int processedCount = 0;
        int removedCount = 0;
        DateTime lastUpdateTime = DateTime.Now;

        foreach (Measurement measurement in measurements)
        {
            int index = ParseSignalReferenceIndex(measurement.SignalReference);

            if (index > lastIndex)
            {
                measurementOps.DeleteRecord(measurement);
                removedCount++;
            }

            processedCount++;

            if ((DateTime.Now - lastUpdateTime).TotalSeconds < 2.0D)
                continue;

            Console.WriteLine($"        >> Checked {processedCount:N0} {signalType} measurements so far...");
            lastUpdateTime = DateTime.Now;
        }

        Console.WriteLine($"        Done! Checked {processedCount:N0} {signalType} measurements, {removedCount:N0} removed");
    }

    /// <summary>
    /// Parses the index from a signal reference formatted as "Acronym-SignalTypeSuffix{Index}".
    /// </summary>
    public static int ParseSignalReferenceIndex(string signalReference)
    {
        return string.IsNullOrEmpty(signalReference) ? 0 : new SignalReference(signalReference).Index;
    }
}
