using System.Net.Http.Json;
using System.Text.Json;
using System.Web;

namespace Synor.Sdk.Database;

/// <summary>
/// Synor Database SDK client for C#.
///
/// Provides multi-model database with Key-Value, Document, Vector, and Time Series stores.
/// </summary>
/// <example>
/// <code>
/// var db = new SynorDatabase(new DatabaseConfig { ApiKey = "your-api-key" });
///
/// // Key-Value operations
/// await db.Kv.SetAsync("mykey", "myvalue");
/// var value = await db.Kv.GetAsync("mykey");
///
/// // Document operations
/// var id = await db.Documents.CreateAsync("users", new { name = "Alice", age = 30 });
/// var doc = await db.Documents.GetAsync("users", id);
///
/// db.Dispose();
/// </code>
/// </example>
public class SynorDatabase : IDisposable
{
    private readonly DatabaseConfig _config;
    private readonly HttpClient _httpClient;
    private readonly JsonSerializerOptions _jsonOptions;
    private bool _disposed;

    public KeyValueStore Kv { get; }
    public DocumentStore Documents { get; }
    public VectorStore Vectors { get; }
    public TimeSeriesStore TimeSeries { get; }

    public SynorDatabase(DatabaseConfig config)
    {
        _config = config;
        _httpClient = new HttpClient
        {
            BaseAddress = new Uri(config.Endpoint),
            Timeout = config.Timeout
        };
        _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {config.ApiKey}");
        _httpClient.DefaultRequestHeaders.Add("X-SDK-Version", "csharp/0.1.0");

        _jsonOptions = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
            PropertyNameCaseInsensitive = true
        };

        Kv = new KeyValueStore(this);
        Documents = new DocumentStore(this);
        Vectors = new VectorStore(this);
        TimeSeries = new TimeSeriesStore(this);
    }

    /// <summary>
    /// Key-Value store operations.
    /// </summary>
    public class KeyValueStore
    {
        private readonly SynorDatabase _db;

        internal KeyValueStore(SynorDatabase db) => _db = db;

        public async Task<object?> GetAsync(string key, CancellationToken ct = default)
        {
            var response = await _db.GetAsync<KvGetResponse>($"/kv/{Uri.EscapeDataString(key)}", ct);
            return response.Value;
        }

        public async Task SetAsync(string key, object value, int? ttl = null, CancellationToken ct = default)
        {
            var body = new Dictionary<string, object> { ["key"] = key, ["value"] = value };
            if (ttl.HasValue) body["ttl"] = ttl.Value;
            await _db.PutAsync<object>($"/kv/{Uri.EscapeDataString(key)}", body, ct);
        }

        public async Task DeleteAsync(string key, CancellationToken ct = default)
        {
            await _db.DeleteAsync($"/kv/{Uri.EscapeDataString(key)}", ct);
        }

        public async Task<List<KeyValue>> ListAsync(string prefix, CancellationToken ct = default)
        {
            var response = await _db.GetAsync<KvListResponse>($"/kv?prefix={Uri.EscapeDataString(prefix)}", ct);
            return response.Items ?? new List<KeyValue>();
        }
    }

    /// <summary>
    /// Document store operations.
    /// </summary>
    public class DocumentStore
    {
        private readonly SynorDatabase _db;

        internal DocumentStore(SynorDatabase db) => _db = db;

        public async Task<string> CreateAsync(string collection, object document, CancellationToken ct = default)
        {
            var response = await _db.PostAsync<CreateDocumentResponse>(
                $"/collections/{Uri.EscapeDataString(collection)}/documents", document, ct);
            return response.Id;
        }

        public async Task<Document> GetAsync(string collection, string id, CancellationToken ct = default)
        {
            return await _db.GetAsync<Document>(
                $"/collections/{Uri.EscapeDataString(collection)}/documents/{Uri.EscapeDataString(id)}", ct);
        }

        public async Task UpdateAsync(string collection, string id, object update, CancellationToken ct = default)
        {
            await _db.PatchAsync<object>(
                $"/collections/{Uri.EscapeDataString(collection)}/documents/{Uri.EscapeDataString(id)}", update, ct);
        }

        public async Task DeleteAsync(string collection, string id, CancellationToken ct = default)
        {
            await _db.DeleteAsync(
                $"/collections/{Uri.EscapeDataString(collection)}/documents/{Uri.EscapeDataString(id)}", ct);
        }

        public async Task<List<Document>> QueryAsync(string collection, DocumentQuery query, CancellationToken ct = default)
        {
            var response = await _db.PostAsync<DocumentListResponse>(
                $"/collections/{Uri.EscapeDataString(collection)}/query", query, ct);
            return response.Documents ?? new List<Document>();
        }
    }

    /// <summary>
    /// Vector store operations.
    /// </summary>
    public class VectorStore
    {
        private readonly SynorDatabase _db;

        internal VectorStore(SynorDatabase db) => _db = db;

        public async Task UpsertAsync(string collection, IEnumerable<VectorEntry> vectors, CancellationToken ct = default)
        {
            await _db.PostAsync<object>(
                $"/vectors/{Uri.EscapeDataString(collection)}/upsert",
                new { vectors = vectors }, ct);
        }

        public async Task<List<SearchResult>> SearchAsync(string collection, double[] vector, int k, CancellationToken ct = default)
        {
            var response = await _db.PostAsync<SearchListResponse>(
                $"/vectors/{Uri.EscapeDataString(collection)}/search",
                new { vector, k }, ct);
            return response.Results ?? new List<SearchResult>();
        }

        public async Task DeleteAsync(string collection, IEnumerable<string> ids, CancellationToken ct = default)
        {
            await _db.DeleteAsync($"/vectors/{Uri.EscapeDataString(collection)}", new { ids = ids }, ct);
        }
    }

    /// <summary>
    /// Time series store operations.
    /// </summary>
    public class TimeSeriesStore
    {
        private readonly SynorDatabase _db;

        internal TimeSeriesStore(SynorDatabase db) => _db = db;

        public async Task WriteAsync(string series, IEnumerable<DataPoint> points, CancellationToken ct = default)
        {
            await _db.PostAsync<object>(
                $"/timeseries/{Uri.EscapeDataString(series)}/write",
                new { points = points }, ct);
        }

        public async Task<List<DataPoint>> QueryAsync(
            string series, TimeRange range, Aggregation? aggregation = null, CancellationToken ct = default)
        {
            var body = new Dictionary<string, object> { ["range"] = range };
            if (aggregation != null) body["aggregation"] = aggregation;

            var response = await _db.PostAsync<DataPointListResponse>(
                $"/timeseries/{Uri.EscapeDataString(series)}/query", body, ct);
            return response.Points ?? new List<DataPoint>();
        }
    }

    /// <summary>
    /// Perform a health check.
    /// </summary>
    public async Task<bool> HealthCheckAsync(CancellationToken ct = default)
    {
        try
        {
            var response = await GetAsync<HealthResponse>("/health", ct);
            return response.Status == "healthy";
        }
        catch
        {
            return false;
        }
    }

    public bool IsClosed => _disposed;

    public void Dispose()
    {
        if (!_disposed)
        {
            _httpClient.Dispose();
            _disposed = true;
        }
        GC.SuppressFinalize(this);
    }

    // Private HTTP methods
    internal async Task<T> GetAsync<T>(string path, CancellationToken ct)
    {
        return await ExecuteWithRetryAsync(async () =>
        {
            var response = await _httpClient.GetAsync(path, ct);
            await EnsureSuccessAsync(response);
            return await response.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)
                ?? throw new DatabaseException("Failed to deserialize response");
        });
    }

    internal async Task<T> PostAsync<T>(string path, object body, CancellationToken ct)
    {
        return await ExecuteWithRetryAsync(async () =>
        {
            var response = await _httpClient.PostAsJsonAsync(path, body, _jsonOptions, ct);
            await EnsureSuccessAsync(response);
            var content = await response.Content.ReadAsStringAsync(ct);
            if (string.IsNullOrEmpty(content)) return default!;
            return JsonSerializer.Deserialize<T>(content, _jsonOptions)!;
        });
    }

    internal async Task<T> PutAsync<T>(string path, object body, CancellationToken ct)
    {
        return await ExecuteWithRetryAsync(async () =>
        {
            var response = await _httpClient.PutAsJsonAsync(path, body, _jsonOptions, ct);
            await EnsureSuccessAsync(response);
            var content = await response.Content.ReadAsStringAsync(ct);
            if (string.IsNullOrEmpty(content)) return default!;
            return JsonSerializer.Deserialize<T>(content, _jsonOptions)!;
        });
    }

    internal async Task<T> PatchAsync<T>(string path, object body, CancellationToken ct)
    {
        return await ExecuteWithRetryAsync(async () =>
        {
            var request = new HttpRequestMessage(HttpMethod.Patch, path)
            {
                Content = JsonContent.Create(body, options: _jsonOptions)
            };
            var response = await _httpClient.SendAsync(request, ct);
            await EnsureSuccessAsync(response);
            var content = await response.Content.ReadAsStringAsync(ct);
            if (string.IsNullOrEmpty(content)) return default!;
            return JsonSerializer.Deserialize<T>(content, _jsonOptions)!;
        });
    }

    internal async Task DeleteAsync(string path, CancellationToken ct)
    {
        await ExecuteWithRetryAsync(async () =>
        {
            var response = await _httpClient.DeleteAsync(path, ct);
            await EnsureSuccessAsync(response);
            return true;
        });
    }

    internal async Task DeleteAsync(string path, object body, CancellationToken ct)
    {
        await ExecuteWithRetryAsync(async () =>
        {
            var request = new HttpRequestMessage(HttpMethod.Delete, path)
            {
                Content = JsonContent.Create(body, options: _jsonOptions)
            };
            var response = await _httpClient.SendAsync(request, ct);
            await EnsureSuccessAsync(response);
            return true;
        });
    }

    private async Task<T> ExecuteWithRetryAsync<T>(Func<Task<T>> operation)
    {
        Exception? lastError = null;

        for (int attempt = 0; attempt < _config.Retries; attempt++)
        {
            try
            {
                return await operation();
            }
            catch (Exception ex)
            {
                lastError = ex;
                if (_config.Debug) Console.WriteLine($"Attempt {attempt + 1} failed: {ex.Message}");
                if (attempt < _config.Retries - 1)
                {
                    await Task.Delay(TimeSpan.FromSeconds(1 << attempt));
                }
            }
        }

        throw lastError ?? new DatabaseException("Unknown error");
    }

    private async Task EnsureSuccessAsync(HttpResponseMessage response)
    {
        if (!response.IsSuccessStatusCode)
        {
            var content = await response.Content.ReadAsStringAsync();
            var error = JsonSerializer.Deserialize<Dictionary<string, object>>(content, _jsonOptions);
            var message = error?.GetValueOrDefault("message")?.ToString() ?? $"HTTP {(int)response.StatusCode}";
            var code = error?.GetValueOrDefault("code")?.ToString();
            throw new DatabaseException(message, code, (int)response.StatusCode);
        }
    }
}
