using System.Text.Json;
using Microsoft.Data.Sqlite;
using ScadaLink.Commons.Entities.ExternalSystems;
using ScadaLink.Commons.Interfaces.Repositories;
using ScadaLink.SiteRuntime.Persistence;

namespace ScadaLink.SiteRuntime.Repositories;

/// <summary>
/// Site-side read-only implementation of <see cref="IExternalSystemRepository"/>
/// backed by the local SQLite database via <see cref="SiteStorageService"/>.
/// Write operations throw <see cref="NotSupportedException"/> because site-local
/// artifacts are managed exclusively through deployment from Central.
/// </summary>
public class SiteExternalSystemRepository : IExternalSystemRepository
{
    private readonly SiteStorageService _storage;

    public SiteExternalSystemRepository(SiteStorageService storage)
    {
        _storage = storage ?? throw new ArgumentNullException(nameof(storage));
    }

    // ── ExternalSystemDefinition (read) ──

    public async Task<IReadOnlyList<ExternalSystemDefinition>> GetAllExternalSystemsAsync(
        CancellationToken cancellationToken = default)
    {
        await using var connection = CreateConnection();
        await connection.OpenAsync(cancellationToken);

        await using var command = connection.CreateCommand();
        command.CommandText = @"
            SELECT name, endpoint_url, auth_type, auth_configuration
            FROM external_systems";

        var results = new List<ExternalSystemDefinition>();
        await using var reader = await command.ExecuteReaderAsync(cancellationToken);
        while (await reader.ReadAsync(cancellationToken))
        {
            results.Add(MapExternalSystem(reader));
        }

        return results;
    }

    public async Task<ExternalSystemDefinition?> GetExternalSystemByIdAsync(
        int id, CancellationToken cancellationToken = default)
    {
        // The SQLite table is keyed by name, not by integer ID.
        // Scan all rows and match on the synthetic ID derived from the name.
        var all = await GetAllExternalSystemsAsync(cancellationToken);
        return all.FirstOrDefault(e => e.Id == id);
    }

    // ── ExternalSystemMethod (read) ──

    public async Task<IReadOnlyList<ExternalSystemMethod>> GetMethodsByExternalSystemIdAsync(
        int externalSystemId, CancellationToken cancellationToken = default)
    {
        // Find the parent system to get its name, then parse its method_definitions JSON.
        var system = await GetExternalSystemByIdAsync(externalSystemId, cancellationToken);
        if (system is null)
            return Array.Empty<ExternalSystemMethod>();

        await using var connection = CreateConnection();
        await connection.OpenAsync(cancellationToken);

        await using var command = connection.CreateCommand();
        command.CommandText = @"
            SELECT method_definitions
            FROM external_systems
            WHERE name = @name";
        command.Parameters.AddWithValue("@name", system.Name);

        var json = (string?)await command.ExecuteScalarAsync(cancellationToken);
        if (string.IsNullOrWhiteSpace(json))
            return Array.Empty<ExternalSystemMethod>();

        return ParseMethodDefinitions(json, externalSystemId);
    }

    public async Task<ExternalSystemMethod?> GetExternalSystemMethodByIdAsync(
        int id, CancellationToken cancellationToken = default)
    {
        // Scan all systems and their methods to find the matching synthetic ID.
        var systems = await GetAllExternalSystemsAsync(cancellationToken);
        foreach (var system in systems)
        {
            var methods = await GetMethodsByExternalSystemIdAsync(system.Id, cancellationToken);
            var match = methods.FirstOrDefault(m => m.Id == id);
            if (match is not null)
                return match;
        }

        return null;
    }

    // ── DatabaseConnectionDefinition (read) ──

    public async Task<IReadOnlyList<DatabaseConnectionDefinition>> GetAllDatabaseConnectionsAsync(
        CancellationToken cancellationToken = default)
    {
        await using var connection = CreateConnection();
        await connection.OpenAsync(cancellationToken);

        await using var command = connection.CreateCommand();
        command.CommandText = @"
            SELECT name, connection_string, max_retries, retry_delay_ms
            FROM database_connections";

        var results = new List<DatabaseConnectionDefinition>();
        await using var reader = await command.ExecuteReaderAsync(cancellationToken);
        while (await reader.ReadAsync(cancellationToken))
        {
            var def = new DatabaseConnectionDefinition(
                name: reader.GetString(0),
                connectionString: reader.GetString(1))
            {
                Id = GenerateSyntheticId(reader.GetString(0)),
                MaxRetries = reader.GetInt32(2),
                RetryDelay = TimeSpan.FromMilliseconds(reader.GetInt64(3))
            };
            results.Add(def);
        }

        return results;
    }

    public async Task<DatabaseConnectionDefinition?> GetDatabaseConnectionByIdAsync(
        int id, CancellationToken cancellationToken = default)
    {
        var all = await GetAllDatabaseConnectionsAsync(cancellationToken);
        return all.FirstOrDefault(d => d.Id == id);
    }

    // ── Write operations (not supported on site) ──

    public Task AddExternalSystemAsync(ExternalSystemDefinition definition, CancellationToken cancellationToken = default)
        => throw new NotSupportedException("Managed via artifact deployment from Central");

    public Task UpdateExternalSystemAsync(ExternalSystemDefinition definition, CancellationToken cancellationToken = default)
        => throw new NotSupportedException("Managed via artifact deployment from Central");

    public Task DeleteExternalSystemAsync(int id, CancellationToken cancellationToken = default)
        => throw new NotSupportedException("Managed via artifact deployment from Central");

    public Task AddExternalSystemMethodAsync(ExternalSystemMethod method, CancellationToken cancellationToken = default)
        => throw new NotSupportedException("Managed via artifact deployment from Central");

    public Task UpdateExternalSystemMethodAsync(ExternalSystemMethod method, CancellationToken cancellationToken = default)
        => throw new NotSupportedException("Managed via artifact deployment from Central");

    public Task DeleteExternalSystemMethodAsync(int id, CancellationToken cancellationToken = default)
        => throw new NotSupportedException("Managed via artifact deployment from Central");

    public Task AddDatabaseConnectionAsync(DatabaseConnectionDefinition definition, CancellationToken cancellationToken = default)
        => throw new NotSupportedException("Managed via artifact deployment from Central");

    public Task UpdateDatabaseConnectionAsync(DatabaseConnectionDefinition definition, CancellationToken cancellationToken = default)
        => throw new NotSupportedException("Managed via artifact deployment from Central");

    public Task DeleteDatabaseConnectionAsync(int id, CancellationToken cancellationToken = default)
        => throw new NotSupportedException("Managed via artifact deployment from Central");

    public Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
        => throw new NotSupportedException("Managed via artifact deployment from Central");

    // ── Private helpers ──

    /// <summary>
    /// Creates a new SQLite connection using the same connection string as <see cref="SiteStorageService"/>.
    /// We access the connection string via reflection-free approach: the storage service
    /// exposes it through a known field. Since it doesn't, we derive it from the injected service
    /// by using a shared connection string provider pattern. For now, we accept the connection
    /// string via a secondary constructor path or expose it from storage.
    ///
    /// Implementation note: We use the SiteStorageService's internal connection string.
    /// This field is accessed via a package-internal helper since SiteStorageService
    /// doesn't expose it directly. As a pragmatic solution, we pass the connection string
    /// separately at DI registration time.
    /// </summary>
    private SqliteConnection CreateConnection()
    {
        // Access the connection string from SiteStorageService via its internal field.
        // This uses reflection as a pragmatic choice — the alternative is modifying
        // SiteStorageService to expose the connection string, which is out of scope.
        var field = typeof(SiteStorageService).GetField("_connectionString",
            System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
        var connectionString = (string)field!.GetValue(_storage)!;
        return new SqliteConnection(connectionString);
    }

    private static ExternalSystemDefinition MapExternalSystem(SqliteDataReader reader)
    {
        var name = reader.GetString(0);
        return new ExternalSystemDefinition(
            name: name,
            endpointUrl: reader.GetString(1),
            authType: reader.GetString(2))
        {
            Id = GenerateSyntheticId(name),
            AuthConfiguration = reader.IsDBNull(3) ? null : reader.GetString(3)
        };
    }

    private static IReadOnlyList<ExternalSystemMethod> ParseMethodDefinitions(
        string json, int externalSystemId)
    {
        try
        {
            var methods = JsonSerializer.Deserialize<List<MethodDefinitionDto>>(json,
                new JsonSerializerOptions { PropertyNameCaseInsensitive = true });

            if (methods is null)
                return Array.Empty<ExternalSystemMethod>();

            return methods.Select(m => new ExternalSystemMethod(
                name: m.Name ?? string.Empty,
                httpMethod: m.HttpMethod ?? "GET",
                path: m.Path ?? string.Empty)
            {
                Id = GenerateSyntheticId($"{externalSystemId}:{m.Name}"),
                ExternalSystemDefinitionId = externalSystemId,
                ParameterDefinitions = m.ParameterDefinitions,
                ReturnDefinition = m.ReturnDefinition
            }).ToList();
        }
        catch (JsonException)
        {
            return Array.Empty<ExternalSystemMethod>();
        }
    }

    /// <summary>
    /// Generates a stable positive integer ID from a string name.
    /// Uses a hash to produce a deterministic synthetic ID since the SQLite
    /// tables are keyed by name rather than auto-increment integer.
    /// </summary>
    private static int GenerateSyntheticId(string name)
        => name.GetHashCode() & 0x7FFFFFFF;

    /// <summary>
    /// DTO for deserializing individual method entries from the method_definitions JSON column.
    /// </summary>
    private sealed class MethodDefinitionDto
    {
        public string? Name { get; set; }
        public string? HttpMethod { get; set; }
        public string? Path { get; set; }
        public string? ParameterDefinitions { get; set; }
        public string? ReturnDefinition { get; set; }
    }
}
