using System.Globalization;
using CliFx.Attributes;
using CliFx.Exceptions;
using CliFx.Infrastructure;
using Opc.Ua;
using ZB.MOM.WW.OtOpcUa.Client.CLI.Helpers;
using ZB.MOM.WW.OtOpcUa.Client.Shared;
using ZB.MOM.WW.OtOpcUa.Client.Shared.Models;

namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Commands;

[Command("historyread", Description = "Read historical data from a node")]
public class HistoryReadCommand : CommandBase
{
    /// <summary>
    /// Creates the historical-data command used to inspect raw or aggregate history for a node.
    /// </summary>
    /// <param name="factory">The factory that creates the shared client service for the command run.</param>
    public HistoryReadCommand(IOpcUaClientServiceFactory factory) : base(factory)
    {
    }

    /// <summary>
    /// Gets the historized node to query.
    /// </summary>
    [CommandOption("node", 'n', Description = "Node ID (e.g. ns=2;s=MyNode)", IsRequired = true)]
    public string NodeId { get; init; } = default!;

    /// <summary>
    /// Gets the optional history start time string supplied by the operator.
    /// </summary>
    [CommandOption("start", Description = "Start time in ISO 8601 UTC format, e.g. 2026-01-15T08:00:00Z (default: 24 hours ago)")]
    public string? StartTime { get; init; }

    /// <summary>
    /// Gets the optional history end time string supplied by the operator.
    /// </summary>
    [CommandOption("end", Description = "End time in ISO 8601 UTC format, e.g. 2026-01-15T09:00:00Z (default: now)")]
    public string? EndTime { get; init; }

    /// <summary>
    /// Gets the maximum number of raw values the command should print.
    /// </summary>
    [CommandOption("max", Description = "Maximum number of values to return")]
    public int MaxValues { get; init; } = 1000;

    /// <summary>
    /// Gets the optional aggregate name used when the operator wants processed history instead of raw samples.
    /// </summary>
    [CommandOption("aggregate", Description = "Aggregate function: Average, Minimum, Maximum, Count, Start, End, StandardDeviation")]
    public string? Aggregate { get; init; }

    /// <summary>
    /// Gets the aggregate bucket interval, in milliseconds, for processed history reads.
    /// </summary>
    [CommandOption("interval", Description = "Processing interval in milliseconds for aggregates")]
    public double IntervalMs { get; init; } = 3600000;

    /// <inheritdoc />
    public override async ValueTask ExecuteAsync(IConsole console)
    {
        ConfigureLogging();
        if (MaxValues <= 0)
            throw new CommandException($"--max must be greater than 0 (was {MaxValues}).");
        if (!string.IsNullOrEmpty(Aggregate) && IntervalMs <= 0)
            throw new CommandException($"--interval must be greater than 0 (was {IntervalMs}).");

        NodeId nodeId;
        try
        {
            nodeId = NodeIdParser.ParseRequired(NodeId);
        }
        catch (Exception ex) when (ex is FormatException or ArgumentException)
        {
            throw new CommandException($"Invalid --node value: {ex.Message}");
        }

        DateTime start, end;
        try
        {
            start = string.IsNullOrEmpty(StartTime)
                ? DateTime.UtcNow.AddHours(-24)
                : DateTime.Parse(StartTime, CultureInfo.InvariantCulture,
                    DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
        }
        catch (FormatException ex)
        {
            throw new CommandException($"Invalid --start value '{StartTime}': {ex.Message}. Expected ISO 8601 UTC format, e.g. 2026-01-15T08:00:00Z.");
        }

        try
        {
            end = string.IsNullOrEmpty(EndTime)
                ? DateTime.UtcNow
                : DateTime.Parse(EndTime, CultureInfo.InvariantCulture,
                    DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
        }
        catch (FormatException ex)
        {
            throw new CommandException($"Invalid --end value '{EndTime}': {ex.Message}. Expected ISO 8601 UTC format, e.g. 2026-01-15T08:00:00Z.");
        }

        AggregateType aggregateType = default;
        if (!string.IsNullOrEmpty(Aggregate))
        {
            try
            {
                aggregateType = ParseAggregateType(Aggregate);
            }
            catch (ArgumentException ex)
            {
                throw new CommandException($"Invalid --aggregate value: {ex.Message}");
            }
        }

        IOpcUaClientService? service = null;
        try
        {
            var ct = console.RegisterCancellationHandler();
            (service, _) = await CreateServiceAndConnectAsync(ct);

            IReadOnlyList<DataValue> values;

            if (string.IsNullOrEmpty(Aggregate))
            {
                await console.Output.WriteLineAsync(
                    $"History for {NodeId} ({start:yyyy-MM-dd HH:mm} -> {end:yyyy-MM-dd HH:mm})");
                values = await service.HistoryReadRawAsync(nodeId, start, end, MaxValues, ct);
            }
            else
            {
                await console.Output.WriteLineAsync(
                    $"History for {NodeId} ({Aggregate}, interval={IntervalMs}ms)");
                values = await service.HistoryReadAggregateAsync(
                    nodeId, start, end, aggregateType, IntervalMs, ct);
            }

            await console.Output.WriteLineAsync();
            await console.Output.WriteLineAsync($"{"Timestamp",-35} {"Value",-15} {"Status"}");

            foreach (var dv in values)
            {
                var status = StatusCode.IsGood(dv.StatusCode) ? "Good"
                    : StatusCode.IsBad(dv.StatusCode) ? "Bad"
                    : "Uncertain";
                await console.Output.WriteLineAsync(
                    $"{dv.SourceTimestamp.ToString("O"),-35} {dv.Value,-15} {status}");
            }

            await console.Output.WriteLineAsync();
            await console.Output.WriteLineAsync($"{values.Count} values returned.");
        }
        finally
        {
            if (service != null)
            {
                await service.DisconnectAsync();
                service.Dispose();
            }
        }
    }

    private static AggregateType ParseAggregateType(string name)
    {
        return name.Trim().ToLowerInvariant() switch
        {
            "average" or "avg" => AggregateType.Average,
            "minimum" or "min" => AggregateType.Minimum,
            "maximum" or "max" => AggregateType.Maximum,
            "count" => AggregateType.Count,
            "start" or "first" => AggregateType.Start,
            "end" or "last" => AggregateType.End,
            "standarddeviation" or "stddev" or "stdev" => AggregateType.StandardDeviation,
            _ => throw new ArgumentException(
                $"Unknown aggregate: '{name}'. Supported: Average, Minimum, Maximum, Count, Start, End, StandardDeviation")
        };
    }
}
