using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend.Galaxy;

/// <summary>
///     SQL access to the Galaxy <c>ZB</c> repository — port of v1 <c>GalaxyRepositoryService</c>.
///     The two SQL bodies (Hierarchy + Attributes) are byte-for-byte identical to v1 so the
///     queries surface the same row set at parity time. Extended-attributes and scope-filter
///     queries from v1 are intentionally not ported yet — they're refinements that aren't on
///     the Phase 2 critical path.
/// </summary>
public sealed class GalaxyRepository(GalaxyRepositoryOptions options)
{
    public async Task<bool> TestConnectionAsync(CancellationToken ct = default)
    {
        try
        {
            using var conn = new SqlConnection(options.ConnectionString);
            await conn.OpenAsync(ct).ConfigureAwait(false);
            using var cmd = new SqlCommand("SELECT 1", conn) { CommandTimeout = options.CommandTimeoutSeconds };
            var result = await cmd.ExecuteScalarAsync(ct).ConfigureAwait(false);
            return result is int i && i == 1;
        }
        catch (SqlException) { return false; }
        catch (InvalidOperationException) { return false; }
    }

    public async Task<DateTime?> GetLastDeployTimeAsync(CancellationToken ct = default)
    {
        using var conn = new SqlConnection(options.ConnectionString);
        await conn.OpenAsync(ct).ConfigureAwait(false);
        using var cmd = new SqlCommand("SELECT time_of_last_deploy FROM galaxy", conn)
        { CommandTimeout = options.CommandTimeoutSeconds };
        var result = await cmd.ExecuteScalarAsync(ct).ConfigureAwait(false);
        return result is DateTime dt ? dt : null;
    }

    public async Task<List<GalaxyHierarchyRow>> GetHierarchyAsync(CancellationToken ct = default)
    {
        var rows = new List<GalaxyHierarchyRow>();

        using var conn = new SqlConnection(options.ConnectionString);
        await conn.OpenAsync(ct).ConfigureAwait(false);

        using var cmd = new SqlCommand(HierarchySql, conn) { CommandTimeout = options.CommandTimeoutSeconds };
        using var reader = await cmd.ExecuteReaderAsync(ct).ConfigureAwait(false);

        while (await reader.ReadAsync(ct).ConfigureAwait(false))
        {
            var templateChainRaw = reader.IsDBNull(8) ? string.Empty : reader.GetString(8);
            var templateChain = templateChainRaw.Length == 0
                ? Array.Empty<string>()
                : templateChainRaw.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
                    .Select(s => s.Trim())
                    .Where(s => s.Length > 0)
                    .ToArray();

            rows.Add(new GalaxyHierarchyRow
            {
                GobjectId = Convert.ToInt32(reader.GetValue(0)),
                TagName = reader.GetString(1),
                ContainedName = reader.IsDBNull(2) ? string.Empty : reader.GetString(2),
                BrowseName = reader.GetString(3),
                ParentGobjectId = Convert.ToInt32(reader.GetValue(4)),
                IsArea = Convert.ToInt32(reader.GetValue(5)) == 1,
                CategoryId = Convert.ToInt32(reader.GetValue(6)),
                HostedByGobjectId = Convert.ToInt32(reader.GetValue(7)),
                TemplateChain = templateChain,
            });
        }
        return rows;
    }

    public async Task<List<GalaxyAttributeRow>> GetAttributesAsync(CancellationToken ct = default)
    {
        var rows = new List<GalaxyAttributeRow>();

        using var conn = new SqlConnection(options.ConnectionString);
        await conn.OpenAsync(ct).ConfigureAwait(false);

        using var cmd = new SqlCommand(AttributesSql, conn) { CommandTimeout = options.CommandTimeoutSeconds };
        using var reader = await cmd.ExecuteReaderAsync(ct).ConfigureAwait(false);

        while (await reader.ReadAsync(ct).ConfigureAwait(false))
        {
            rows.Add(new GalaxyAttributeRow
            {
                GobjectId = Convert.ToInt32(reader.GetValue(0)),
                TagName = reader.GetString(1),
                AttributeName = reader.GetString(2),
                FullTagReference = reader.GetString(3),
                MxDataType = Convert.ToInt32(reader.GetValue(4)),
                DataTypeName = reader.IsDBNull(5) ? null : reader.GetString(5),
                IsArray = Convert.ToInt32(reader.GetValue(6)) == 1,
                ArrayDimension = reader.IsDBNull(7) ? (int?)null : Convert.ToInt32(reader.GetValue(7)),
                MxAttributeCategory = Convert.ToInt32(reader.GetValue(8)),
                SecurityClassification = Convert.ToInt32(reader.GetValue(9)),
                IsHistorized = Convert.ToInt32(reader.GetValue(10)) == 1,
                IsAlarm = Convert.ToInt32(reader.GetValue(11)) == 1,
            });
        }
        return rows;
    }

    private const string HierarchySql = @"
;WITH template_chain AS (
    SELECT g.gobject_id AS instance_gobject_id, t.gobject_id AS template_gobject_id,
           t.tag_name AS template_tag_name, t.derived_from_gobject_id, 0 AS depth
    FROM gobject g
    INNER JOIN gobject t ON t.gobject_id = g.derived_from_gobject_id
    WHERE g.is_template = 0 AND g.deployed_package_id <> 0 AND g.derived_from_gobject_id <> 0
    UNION ALL
    SELECT tc.instance_gobject_id, t.gobject_id, t.tag_name, t.derived_from_gobject_id, tc.depth + 1
    FROM template_chain tc
    INNER JOIN gobject t ON t.gobject_id = tc.derived_from_gobject_id
    WHERE tc.derived_from_gobject_id <> 0 AND tc.depth < 10
)
SELECT DISTINCT
    g.gobject_id,
    g.tag_name,
    g.contained_name,
    CASE WHEN g.contained_name IS NULL OR g.contained_name = ''
         THEN g.tag_name
         ELSE g.contained_name
    END AS browse_name,
    CASE WHEN g.contained_by_gobject_id = 0
         THEN g.area_gobject_id
         ELSE g.contained_by_gobject_id
    END AS parent_gobject_id,
    CASE WHEN td.category_id = 13
         THEN 1
         ELSE 0
    END AS is_area,
    td.category_id AS category_id,
    g.hosted_by_gobject_id AS hosted_by_gobject_id,
    ISNULL(
        STUFF((
            SELECT '|' + tc.template_tag_name
            FROM template_chain tc
            WHERE tc.instance_gobject_id = g.gobject_id
            ORDER BY tc.depth
            FOR XML PATH('')
        ), 1, 1, ''),
        ''
    ) AS template_chain
FROM gobject g
INNER JOIN template_definition td
    ON g.template_definition_id = td.template_definition_id
WHERE td.category_id IN (1, 3, 4, 10, 11, 13, 17, 24, 26)
    AND g.is_template = 0
    AND g.deployed_package_id <> 0
ORDER BY parent_gobject_id, g.tag_name";

    private const string AttributesSql = @"
;WITH deployed_package_chain AS (
    SELECT g.gobject_id, p.package_id, p.derived_from_package_id, 0 AS depth
    FROM gobject g
    INNER JOIN package p ON p.package_id = g.deployed_package_id
    WHERE g.is_template = 0 AND g.deployed_package_id <> 0
    UNION ALL
    SELECT dpc.gobject_id, p.package_id, p.derived_from_package_id, dpc.depth + 1
    FROM deployed_package_chain dpc
    INNER JOIN package p ON p.package_id = dpc.derived_from_package_id
    WHERE dpc.derived_from_package_id <> 0 AND dpc.depth < 10
)
SELECT gobject_id, tag_name, attribute_name, full_tag_reference,
    mx_data_type, data_type_name, is_array, array_dimension,
    mx_attribute_category, security_classification, is_historized, is_alarm
FROM (
    SELECT
        dpc.gobject_id,
        g.tag_name,
        da.attribute_name,
        g.tag_name + '.' + da.attribute_name
            + CASE WHEN da.is_array = 1 THEN '[]' ELSE '' END
            AS full_tag_reference,
        da.mx_data_type,
        dt.description AS data_type_name,
        da.is_array,
        CASE WHEN da.is_array = 1
             THEN CONVERT(int, CONVERT(varbinary(2),
                  SUBSTRING(da.mx_value, 15, 2) + SUBSTRING(da.mx_value, 13, 2), 2))
             ELSE NULL
        END AS array_dimension,
        da.mx_attribute_category,
        da.security_classification,
        CASE WHEN EXISTS (
            SELECT 1 FROM deployed_package_chain dpc2
            INNER JOIN primitive_instance pi ON pi.package_id = dpc2.package_id AND pi.primitive_name = da.attribute_name
            INNER JOIN primitive_definition pd ON pd.primitive_definition_id = pi.primitive_definition_id AND pd.primitive_name = 'HistoryExtension'
            WHERE dpc2.gobject_id = dpc.gobject_id
        ) THEN 1 ELSE 0 END AS is_historized,
        CASE WHEN EXISTS (
            SELECT 1 FROM deployed_package_chain dpc2
            INNER JOIN primitive_instance pi ON pi.package_id = dpc2.package_id AND pi.primitive_name = da.attribute_name
            INNER JOIN primitive_definition pd ON pd.primitive_definition_id = pi.primitive_definition_id AND pd.primitive_name = 'AlarmExtension'
            WHERE dpc2.gobject_id = dpc.gobject_id
        ) THEN 1 ELSE 0 END AS is_alarm,
        ROW_NUMBER() OVER (
            PARTITION BY dpc.gobject_id, da.attribute_name
            ORDER BY dpc.depth
        ) AS rn
    FROM deployed_package_chain dpc
    INNER JOIN dynamic_attribute da
        ON da.package_id = dpc.package_id
    INNER JOIN gobject g
        ON g.gobject_id = dpc.gobject_id
    INNER JOIN template_definition td
        ON td.template_definition_id = g.template_definition_id
    LEFT JOIN data_type dt
        ON dt.mx_data_type = da.mx_data_type
    WHERE td.category_id IN (1, 3, 4, 10, 11, 13, 17, 24, 26)
        AND da.attribute_name NOT LIKE '[_]%'
        AND da.attribute_name NOT LIKE '%.Description'
        AND da.mx_attribute_category IN (2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 24)
) ranked
WHERE rn = 1
ORDER BY tag_name, attribute_name";
}
