using Sonex.Data.Records;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Logger = Sonex.Client.Helpers.Logger;

namespace Sonex.Client.Controls.DataGridView;

// Odpowiedzialnosc pliku: serializacja/deserializacja ukladu kolumn i zapis/odczyt preferencji.

public sealed partial class DataGridView
{
    private static readonly JsonSerializerOptions LayoutWriteJsonOptions = new()
    {
        WriteIndented = false
    };
    private static readonly JsonReaderOptions LayoutReaderOptions = new()
    {
        CommentHandling = JsonCommentHandling.Skip,
        AllowTrailingCommas = true,
        MaxDepth = 64
    };

    private string? _layoutJson;
    private bool _IsApplyingLayoutFromJson;
    private bool _isLoadingLayoutFromPreferences;
    private string? _lastAutoLayoutPreferenceLoadKey;
    private static readonly object LayoutPreferenceCacheLoadLock = new();
    private static Task<bool>? LayoutPreferenceCacheLoadTask;

    public string? LayoutPreferenceKey { get; set; }
    public string? LayoutNavigationAddress { get; set; }

    public string? LayoutJson
    {
        get => _layoutJson;
        set
        {
            _layoutJson = value;
            if (_Columns.Count > 0 && !_NeedGenerateHeaders)
            {
                ApplyLayoutFromJson(_layoutJson);
            }
        }
    }

    public string ExportLayoutToJson()
    {
        try
        {
            var payload = new DataGridLayoutState
            {
                Version = 1,
                Columns = _Columns
                    .OrderBy(c => c.Index)
                    .Select(c =>
                    {
                        var state = DecodeLayoutIndexState(c.Index);
                        return new DataGridLayoutColumnState
                        {
                            Name = c.Name,
                            Index = c.Index,
                            BaseIndex = ExtractLayoutBaseIndex(c.Index),
                            IsPinned = state.IsPinned,
                            IsHidden = state.IsHidden,
                            Width = c.ManualWidth
                        };
                    })
                    .ToList()
            };

            return JsonSerializer.Serialize(payload, LayoutWriteJsonOptions);
        }
        catch (Exception ex)
        {
            Logger.Error(
                ex,
                source: nameof(DataGridView),
                operation: "Layout.ExportLayoutToJson");
            return string.Empty;
        }
    }

    public bool ApplyLayoutFromJson(string? json)
    {
        // Guardy chronią przed rekurencją i próbą zastosowania layoutu zanim kolumny będą gotowe.
        if (_IsApplyingLayoutFromJson || string.IsNullOrWhiteSpace(json) || _Columns.Count == 0)
        {
            return false;
        }

        try
        {
            _IsApplyingLayoutFromJson = true;
            if (!TryParseLayoutColumnsFromJson(json, out var parsedColumns) || parsedColumns.Count == 0)
            {
                return false;
            }

            var columnsByName = _Columns
                .Where(c => !string.IsNullOrWhiteSpace(c.Name))
                .GroupBy(c => c.Name, StringComparer.OrdinalIgnoreCase)
                .ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);

            var changed = false;
            foreach (var state in parsedColumns)
            {
                if (state == null || string.IsNullOrWhiteSpace(state.Name))
                {
                    continue;
                }

                if (!columnsByName.TryGetValue(state.Name, out var column))
                {
                    continue;
                }

                var currentState = DecodeLayoutIndexState(column.Index);
                var nextIndex = ResolveLayoutIndex(state, column.Index, currentState);
                if (nextIndex.HasValue && column.Index != nextIndex.Value)
                {
                    column.Index = nextIndex.Value;
                    changed = true;
                }

                if (state.Width.HasValue)
                {
                    var width = state.Width.Value;
                    if (double.IsFinite(width) && width > 0)
                    {
                        if (!column.ManualWidth.HasValue || Math.Abs(column.ManualWidth.Value - width) > 0.1d)
                        {
                            column.ManualWidth = width;
                            changed = true;
                        }
                    }
                }
            }

            if (!changed)
            {
                return false;
            }

            _NeedCalcHeaders = true;
            _NeedCalcHorizontalScroll = true;
            if (!_IsGeneratingHeaders)
            {
                RequestRender();
            }
            return true;
        }
        catch (Exception ex)
        {
            Logger.Error(
                ex,
                source: nameof(DataGridView),
                operation: "Layout.ApplyLayoutFromJson");
            return false;
        }
        finally
        {
            _IsApplyingLayoutFromJson = false;
        }
    }

    private static bool TryParseLayoutColumnsFromJson(string json, out List<DataGridLayoutColumnState> columns)
    {
        columns = new List<DataGridLayoutColumnState>();

        if (string.IsNullOrWhiteSpace(json))
        {
            return false;
        }

        // Limit rozmiaru payloadu chroni przed przypadkowym/niepoprawnym bardzo dużym JSON-em.
        if (json.Length > 2_000_000)
        {
            return false;
        }

        try
        {
            var utf8 = Encoding.UTF8.GetBytes(json);
            var reader = new Utf8JsonReader(utf8, LayoutReaderOptions);

            if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject)
            {
                return false;
            }

            while (reader.Read())
            {
                if (reader.TokenType == JsonTokenType.EndObject)
                {
                    return true;
                }

                if (reader.TokenType != JsonTokenType.PropertyName)
                {
                    continue;
                }

                var propertyName = reader.GetString();
                if (!reader.Read())
                {
                    return false;
                }

                if (propertyName != null && propertyName.Equals("columns", StringComparison.OrdinalIgnoreCase))
                {
                    if (reader.TokenType != JsonTokenType.StartArray)
                    {
                        SkipJsonValue(ref reader);
                        continue;
                    }

                    while (reader.Read())
                    {
                        if (reader.TokenType == JsonTokenType.EndArray)
                        {
                            break;
                        }

                        if (reader.TokenType != JsonTokenType.StartObject)
                        {
                            SkipJsonValue(ref reader);
                            continue;
                        }

                        if (TryReadLayoutColumnState(ref reader, out var state)
                            && state != null
                            && !string.IsNullOrWhiteSpace(state.Name))
                        {
                            columns.Add(state);
                        }
                    }
                }
                else
                {
                    SkipJsonValue(ref reader);
                }
            }

            return true;
        }
        catch
        {
            return false;
        }
    }

    private static bool TryReadLayoutColumnState(ref Utf8JsonReader reader, out DataGridLayoutColumnState? state)
    {
        state = new DataGridLayoutColumnState();

        while (reader.Read())
        {
            if (reader.TokenType == JsonTokenType.EndObject)
            {
                return true;
            }

            if (reader.TokenType != JsonTokenType.PropertyName)
            {
                continue;
            }

            var propertyName = reader.GetString();
            if (!reader.Read())
            {
                return false;
            }

            if (propertyName != null && propertyName.Equals("name", StringComparison.OrdinalIgnoreCase))
            {
                if (reader.TokenType == JsonTokenType.String)
                {
                    state.Name = reader.GetString();
                }

                continue;
            }

            if (propertyName != null && propertyName.Equals("index", StringComparison.OrdinalIgnoreCase))
            {
                if (TryReadInt(ref reader, out var parsed))
                {
                    state.Index = parsed;
                }

                continue;
            }

            if (propertyName != null && propertyName.Equals("baseIndex", StringComparison.OrdinalIgnoreCase))
            {
                if (TryReadInt(ref reader, out var parsed))
                {
                    state.BaseIndex = parsed;
                }

                continue;
            }

            if (propertyName != null && propertyName.Equals("isPinned", StringComparison.OrdinalIgnoreCase))
            {
                if (TryReadBool(ref reader, out var parsed))
                {
                    state.IsPinned = parsed;
                }

                continue;
            }

            if (propertyName != null && propertyName.Equals("isHidden", StringComparison.OrdinalIgnoreCase))
            {
                if (TryReadBool(ref reader, out var parsed))
                {
                    state.IsHidden = parsed;
                }

                continue;
            }

            if (propertyName != null && propertyName.Equals("width", StringComparison.OrdinalIgnoreCase))
            {
                if (TryReadDouble(ref reader, out var parsed))
                {
                    state.Width = parsed;
                }

                continue;
            }

            SkipJsonValue(ref reader);
        }

        return false;
    }

    private static void SkipJsonValue(ref Utf8JsonReader reader)
    {
        if (reader.TokenType == JsonTokenType.StartObject || reader.TokenType == JsonTokenType.StartArray)
        {
            reader.Skip();
        }
    }

    private static bool TryReadInt(ref Utf8JsonReader reader, out int value)
    {
        if (reader.TokenType == JsonTokenType.Number)
        {
            return reader.TryGetInt32(out value);
        }

        if (reader.TokenType == JsonTokenType.String)
        {
            var raw = reader.GetString();
            return int.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out value)
                || int.TryParse(raw, NumberStyles.Integer, CultureInfo.CurrentCulture, out value);
        }

        value = default;
        return false;
    }

    private static bool TryReadDouble(ref Utf8JsonReader reader, out double value)
    {
        if (reader.TokenType == JsonTokenType.Number)
        {
            return reader.TryGetDouble(out value);
        }

        if (reader.TokenType == JsonTokenType.String)
        {
            var raw = reader.GetString();
            return double.TryParse(raw, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out value)
                || double.TryParse(raw, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.CurrentCulture, out value);
        }

        value = default;
        return false;
    }

    private static bool TryReadBool(ref Utf8JsonReader reader, out bool value)
    {
        if (reader.TokenType == JsonTokenType.True)
        {
            value = true;
            return true;
        }

        if (reader.TokenType == JsonTokenType.False)
        {
            value = false;
            return true;
        }

        if (reader.TokenType == JsonTokenType.String)
        {
            var raw = reader.GetString();
            if (bool.TryParse(raw, out value))
            {
                return true;
            }

            if (string.Equals(raw, "1", StringComparison.Ordinal))
            {
                value = true;
                return true;
            }

            if (string.Equals(raw, "0", StringComparison.Ordinal))
            {
                value = false;
                return true;
            }
        }

        value = default;
        return false;
    }

    public bool ApplyLayoutFromConfiguredSource()
    {
        try
        {
            if (!string.IsNullOrWhiteSpace(LayoutJson))
            {
                return ApplyLayoutFromJson(LayoutJson);
            }

            if (!TryGetLayoutPreferenceKey(out var key))
            {
                return false;
            }

            if (!AccountPreferenceRecord.TryGetCachedValue(key, out var cachedJson))
            {
                StartAutoLoadLayoutFromPreferences(key);
                return false;
            }

            if (string.IsNullOrWhiteSpace(cachedJson))
            {
                return false;
            }

            return ApplyLayoutFromJson(cachedJson);
        }
        catch (Exception ex)
        {
            Logger.Error(
                ex,
                source: nameof(DataGridView),
                operation: "Layout.ApplyLayoutFromConfiguredSource");
            return false;
        }
    }

    public Task<bool> RestoreLayoutFromPreferencesAsync()
    {
        return LoadLayoutFromPreferencesAsync(forceDatabaseReload: true);
    }

    private void StartAutoLoadLayoutFromPreferences(string key)
    {
        if (_isLoadingLayoutFromPreferences)
        {
            return;
        }

        if (string.Equals(_lastAutoLayoutPreferenceLoadKey, key, StringComparison.OrdinalIgnoreCase))
        {
            return;
        }

        _lastAutoLayoutPreferenceLoadKey = key;
        _ = LoadLayoutFromPreferencesAsync(forceDatabaseReload: false);
    }

    private async Task<bool> LoadLayoutFromPreferencesAsync(bool forceDatabaseReload)
    {
        if (!TryGetLayoutPreferenceKey(out var key))
        {
            return false;
        }

        if (!forceDatabaseReload
            && AccountPreferenceRecord.TryGetCachedValue(key, out var cachedJson)
            && !string.IsNullOrWhiteSpace(cachedJson))
        {
            return await ApplyLayoutFromJsonOnUiThreadAsync(cachedJson);
        }

        if (_isLoadingLayoutFromPreferences)
        {
            return false;
        }

        _isLoadingLayoutFromPreferences = true;
        try
        {
            var loaded = forceDatabaseReload
                ? await ReloadLayoutPreferencesCacheAsync()
                : await EnsureLayoutPreferencesCacheLoadedAsync();

            if (!loaded)
            {
                if (!forceDatabaseReload)
                {
                    _lastAutoLayoutPreferenceLoadKey = null;
                }

                return false;
            }

            if (!AccountPreferenceRecord.TryGetCachedValue(key, out var databaseJson)
                || string.IsNullOrWhiteSpace(databaseJson))
            {
                return false;
            }

            return await ApplyLayoutFromJsonOnUiThreadAsync(databaseJson);
        }
        catch (Exception ex)
        {
            Logger.Error(
                ex,
                source: nameof(DataGridView),
                operation: "Layout.LoadLayoutFromPreferencesAsync");
            return false;
        }
        finally
        {
            _isLoadingLayoutFromPreferences = false;
        }
    }

    private async Task<bool> ApplyLayoutFromJsonOnUiThreadAsync(string json)
    {
        if (DispatcherQueue == null || DispatcherQueue.HasThreadAccess)
        {
            return ApplyLayoutFromJson(json);
        }

        var taskSource = new TaskCompletionSource<bool>();
        var queued = DispatcherQueue.TryEnqueue(() =>
        {
            try
            {
                taskSource.TrySetResult(ApplyLayoutFromJson(json));
            }
            catch (Exception ex)
            {
                taskSource.TrySetException(ex);
            }
        });

        if (!queued)
        {
            return false;
        }

        return await taskSource.Task;
    }

    private static Task<bool> EnsureLayoutPreferencesCacheLoadedAsync()
    {
        lock (LayoutPreferenceCacheLoadLock)
        {
            if (LayoutPreferenceCacheLoadTask == null
                || LayoutPreferenceCacheLoadTask.IsCanceled
                || LayoutPreferenceCacheLoadTask.IsFaulted
                || (LayoutPreferenceCacheLoadTask.IsCompletedSuccessfully && !LayoutPreferenceCacheLoadTask.Result))
            {
                LayoutPreferenceCacheLoadTask = LoadLayoutPreferencesCacheAsync();
            }

            return LayoutPreferenceCacheLoadTask;
        }
    }

    private static Task<bool> ReloadLayoutPreferencesCacheAsync()
    {
        lock (LayoutPreferenceCacheLoadLock)
        {
            LayoutPreferenceCacheLoadTask = LoadLayoutPreferencesCacheAsync();
            return LayoutPreferenceCacheLoadTask;
        }
    }

    private static async Task<bool> LoadLayoutPreferencesCacheAsync()
    {
        var result = await AccountPreferenceRecord.LoadAllToCache();
        return result.Success;
    }

    public async Task<bool> SaveLayoutToPreferencesAsync()
    {
        try
        {
            // Zapisujemy tylko to, co umiemy odtworzyć (kolumny + indeksy + szerokości).
            if (!TryGetLayoutPreferenceKey(out var key))
            {
                return false;
            }

            var json = ExportLayoutToJson();
            if (string.IsNullOrWhiteSpace(json))
            {
                return false;
            }

            var update = await AccountPreferenceRecord.Update(key, json);
            return update.Success && update.Item;
        }
        catch (Exception ex)
        {
            Logger.Error(
                ex,
                source: nameof(DataGridView),
                operation: "Layout.SaveLayoutToPreferencesAsync");
            return false;
        }
    }

    public string GetLayoutPreferenceKeyOrDefault()
    {
        if (TryGetLayoutPreferenceKey(out var key))
        {
            return key;
        }

        return "DataGridView.DataGridView";
    }

    private bool TryGetLayoutPreferenceKey(out string key)
    {
        // Kolejność priorytetu: jawny klucz > adres nawigacji + nazwa grida.
        if (!string.IsNullOrWhiteSpace(LayoutPreferenceKey))
        {
            key = LayoutPreferenceKey.Trim();
            return true;
        }

        var navigationAddress = ResolveLayoutNavigationAddress();
        if (string.IsNullOrWhiteSpace(navigationAddress))
        {
            navigationAddress = "DataGridView";
        }

        var gridName = ResolveLayoutGridName();
        key = $"{navigationAddress}.{gridName}";
        return true;
    }

    private string ResolveLayoutGridName()
    {
        if (!string.IsNullOrWhiteSpace(Name))
        {
            return Name.Trim();
        }

        return "DataGridView";
    }

    private string ResolveLayoutNavigationAddress()
    {
        if (!string.IsNullOrWhiteSpace(LayoutNavigationAddress))
        {
            return LayoutNavigationAddress.Trim();
        }

        // Ujednolicenie z eksportem: bazowa nazwa kontekstu (strony) jest liczona
        // dokładnie tą samą logiką, co auto-nazwa pliku.
        return ResolveExportContextName(navigationName: null, fileNamePrefix: null);
    }

    private static int? ResolveLayoutIndex(
        DataGridLayoutColumnState state,
        int currentIndex,
        (bool IsHidden, bool IsPinned) currentState)
    {
        if (state.Index.HasValue)
        {
            return state.Index.Value;
        }

        var baseIndex = state.BaseIndex ?? ExtractLayoutBaseIndex(currentIndex);
        if (baseIndex < 0)
        {
            baseIndex = 0;
        }

        var isPinned = state.IsPinned ?? currentState.IsPinned;
        var isHidden = state.IsHidden ?? currentState.IsHidden;
        return ComposeLayoutIndex(baseIndex, isPinned, isHidden);
    }

    private static (bool IsHidden, bool IsPinned) DecodeLayoutIndexState(int index)
    {
        var normalized = index;
        var isHidden = normalized >= 1000000;
        if (isHidden)
        {
            normalized -= 1100000;
        }

        var isPinned = normalized < 0;
        return (isHidden, isPinned);
    }

    private static int ExtractLayoutBaseIndex(int index)
    {
        var normalized = index;
        if (normalized >= 1000000)
        {
            normalized -= 1100000;
        }

        if (normalized < 0)
        {
            normalized += 100000;
        }

        return Math.Max(0, normalized);
    }

    private static int ComposeLayoutIndex(int baseIndex, bool isPinned, bool isHidden)
    {
        var index = baseIndex;
        if (isPinned)
        {
            index -= 100000;
        }

        if (isHidden)
        {
            index += 1100000;
        }

        return index;
    }

    private sealed class DataGridLayoutState
    {
        public int Version { get; set; } = 1;
        public List<DataGridLayoutColumnState> Columns { get; set; } = new();
    }

    private sealed class DataGridLayoutColumnState
    {
        public string? Name { get; set; }
        public int? Index { get; set; }
        public int? BaseIndex { get; set; }
        public bool? IsPinned { get; set; }
        public bool? IsHidden { get; set; }
        public double? Width { get; set; }
    }
}
