using DevExpress.DashboardWeb;
using Microsoft.Data.SqlClient;
using System.Data;
using System.Text;
using System.Xml.Linq;

namespace DbFirst.API.Dashboards;

public sealed class SqlDashboardStorage : IEditableDashboardStorage
{
    private readonly string _connectionString;
    private readonly string _tableName;
    private readonly Func<string?>? _userProvider;
    private readonly IDashboardChangeNotifier? _notifier;

    public SqlDashboardStorage(string connectionString, string tableName = "TBDD_SMF_CONFIG", Func<string?>? userProvider = null, IDashboardChangeNotifier? notifier = null)
    {
        _connectionString = connectionString;
        _tableName = tableName;
        _userProvider = userProvider;
        _notifier = notifier;
    }

    public IEnumerable<DashboardInfo> GetAvailableDashboardsInfo()
    {
        var dashboards = new List<DashboardInfo>();

        using var connection = new SqlConnection(_connectionString);
        using var command = new SqlCommand($"SELECT DashboardId, DashboardName FROM dbo.[{_tableName}] WHERE ACTIVE = 1 ORDER BY DashboardName", connection);

        connection.Open();
        using var reader = command.ExecuteReader();
        while (reader.Read())
        {
            var id = reader.GetString(0);
            var name = reader.GetString(1);
            dashboards.Add(new DashboardInfo { ID = id, Name = name });
        }

        return dashboards;
    }

    public XDocument LoadDashboard(string dashboardId)
    {
        using var connection = new SqlConnection(_connectionString);
        using var command = new SqlCommand($"SELECT DashboardData FROM dbo.[{_tableName}] WHERE DashboardId = @Id AND ACTIVE = 1", connection);
        command.Parameters.Add(new SqlParameter("@Id", SqlDbType.NVarChar, 128) { Value = dashboardId });

        connection.Open();
        var data = command.ExecuteScalar() as byte[];
        if (data == null)
        {
            throw new ArgumentException($"Dashboard '{dashboardId}' not found.");
        }

        var xml = Encoding.UTF8.GetString(data);
        var doc = XDocument.Parse(xml);
        NormalizeCatalogDateDimensions(doc);
        return doc;
    }

    private static void NormalizeCatalogDateDimensions(XDocument doc)
    {
        var dateMembers = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
        {
            "AddedWhen",
            "ChangedWhen"
        };

        foreach (var dimension in doc.Descendants("Dimension"))
        {
            var member = dimension.Attribute("DataMember")?.Value;
            if (member == null || !dateMembers.Contains(member))
            {
                continue;
            }

            var interval = dimension.Attribute("DateTimeGroupInterval")?.Value;
            if (string.IsNullOrWhiteSpace(interval) || string.Equals(interval, "Year", StringComparison.OrdinalIgnoreCase))
            {
                dimension.SetAttributeValue("DateTimeGroupInterval", "DayMonthYear");
            }
        }
    }

    public string AddDashboard(XDocument dashboard, string dashboardName)
    {
        var id = string.IsNullOrWhiteSpace(dashboardName)
            ? Guid.NewGuid().ToString("N")
            : dashboardName;
        var payload = Encoding.UTF8.GetBytes(dashboard.ToString(SaveOptions.DisableFormatting));
        var userName = _userProvider?.Invoke();

        using var connection = new SqlConnection(_connectionString);
        using var command = new SqlCommand($"INSERT INTO dbo.[{_tableName}] (ACTIVE, DashboardId, DashboardName, DashboardData, ADDED_WHO, ADDED_WHEN) VALUES (1, @Id, @Name, @Data, COALESCE(@User, SUSER_SNAME()), SYSUTCDATETIME())", connection);
        command.Parameters.Add(new SqlParameter("@Id", SqlDbType.NVarChar, 128) { Value = id });
        command.Parameters.Add(new SqlParameter("@Name", SqlDbType.NVarChar, 256) { Value = string.IsNullOrWhiteSpace(dashboardName) ? id : dashboardName });
        command.Parameters.Add(new SqlParameter("@Data", SqlDbType.VarBinary, -1) { Value = payload });
        command.Parameters.Add(new SqlParameter("@User", SqlDbType.NVarChar, 50) { Value = (object?)userName ?? DBNull.Value });

        connection.Open();
        command.ExecuteNonQuery();
        _ = _notifier?.NotifyChangedAsync();
        return id;
    }

    public void SaveDashboard(string dashboardId, XDocument dashboard)
    {
        var payload = Encoding.UTF8.GetBytes(dashboard.ToString(SaveOptions.DisableFormatting));
        var userName = _userProvider?.Invoke();

        using var connection = new SqlConnection(_connectionString);
        using var command = new SqlCommand($"UPDATE dbo.[{_tableName}] SET DashboardData = @Data, CHANGED_WHO = COALESCE(@User, SUSER_SNAME()), CHANGED_WHEN = SYSUTCDATETIME() WHERE DashboardId = @Id", connection);
        command.Parameters.Add(new SqlParameter("@Id", SqlDbType.NVarChar, 128) { Value = dashboardId });
        command.Parameters.Add(new SqlParameter("@Data", SqlDbType.VarBinary, -1) { Value = payload });
        command.Parameters.Add(new SqlParameter("@User", SqlDbType.NVarChar, 50) { Value = (object?)userName ?? DBNull.Value });

        connection.Open();
        var rows = command.ExecuteNonQuery();
        if (rows == 0)
        {
            throw new ArgumentException($"Dashboard '{dashboardId}' not found.");
        }

        _ = _notifier?.NotifyChangedAsync();
    }

    public void DeleteDashboard(string dashboardId)
    {
        using var connection = new SqlConnection(_connectionString);
        using var command = new SqlCommand($"DELETE FROM dbo.[{_tableName}] WHERE DashboardId = @Id", connection);
        command.Parameters.Add(new SqlParameter("@Id", SqlDbType.NVarChar, 128) { Value = dashboardId });

        connection.Open();
        command.ExecuteNonQuery();
        _ = _notifier?.NotifyChangedAsync();
    }
}
