using System.Net.Http.Json;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;

namespace SynorCompute;

/// <summary>
/// Synor Compute SDK - C# Client
///
/// Access distributed heterogeneous compute resources (CPU, GPU, TPU, NPU, LPU, FPGA, DSP)
/// for AI/ML workloads at 90% cost reduction compared to traditional cloud.
/// </summary>
/// <example>
/// <code>
/// // Create client
/// using var client = new SynorComputeClient("your-api-key");
///
/// // Matrix multiplication on GPU
/// var a = Tensor.Rand(512, 512);
/// var b = Tensor.Rand(512, 512);
/// var result = await client.MatMulAsync(a, b, new MatMulOptions
/// {
///     Processor = ProcessorType.Gpu,
///     Precision = Precision.Fp16
/// });
///
/// if (result.IsSuccess)
/// {
///     Console.WriteLine($"Time: {result.ExecutionTimeMs}ms");
/// }
///
/// // LLM inference
/// var response = await client.InferenceAsync("llama-3-70b", "Explain quantum computing");
/// Console.WriteLine(response.Result);
///
/// // Streaming inference
/// await foreach (var token in client.InferenceStreamAsync("llama-3-70b", "Write a poem"))
/// {
///     Console.Write(token);
/// }
/// </code>
/// </example>
public sealed class SynorComputeClient : IDisposable
{
    public const string Version = "0.1.0";

    private readonly SynorConfig _config;
    private readonly HttpClient _httpClient;
    private readonly JsonSerializerOptions _jsonOptions;
    private bool _disposed;

    public SynorComputeClient(string apiKey)
        : this(new SynorConfig { ApiKey = apiKey })
    {
    }

    public SynorComputeClient(SynorConfig config)
    {
        _config = config;
        _httpClient = new HttpClient
        {
            BaseAddress = new Uri(config.BaseUrl),
            Timeout = TimeSpan.FromMilliseconds(config.TimeoutMs)
        };
        _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {config.ApiKey}");
        _httpClient.DefaultRequestHeaders.Add("X-SDK-Version", $"csharp/{Version}");

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

    // ==================== Matrix Operations ====================

    public Task<JobResult<Tensor>> MatMulAsync(Tensor a, Tensor b, CancellationToken ct = default)
        => MatMulAsync(a, b, new MatMulOptions(), ct);

    public async Task<JobResult<Tensor>> MatMulAsync(
        Tensor a,
        Tensor b,
        MatMulOptions options,
        CancellationToken ct = default)
    {
        CheckDisposed();

        var body = new
        {
            operation = "matmul",
            a = TensorToDict(a),
            b = TensorToDict(b),
            precision = options.Precision.ToString().ToLower(),
            processor = options.Processor.ToString().ToLower(),
            priority = options.Priority.ToString().ToLower()
        };

        return await PostAsync<JobResult<Tensor>>("/compute", body, ct);
    }

    public async Task<JobResult<Tensor>> Conv2dAsync(
        Tensor input,
        Tensor kernel,
        Conv2dOptions? options = null,
        CancellationToken ct = default)
    {
        CheckDisposed();
        options ??= new Conv2dOptions();

        var body = new
        {
            operation = "conv2d",
            input = TensorToDict(input),
            kernel = TensorToDict(kernel),
            stride = new[] { options.Stride.Item1, options.Stride.Item2 },
            padding = new[] { options.Padding.Item1, options.Padding.Item2 },
            precision = options.Precision.ToString().ToLower()
        };

        return await PostAsync<JobResult<Tensor>>("/compute", body, ct);
    }

    public async Task<JobResult<Tensor>> AttentionAsync(
        Tensor query,
        Tensor key,
        Tensor value,
        AttentionOptions? options = null,
        CancellationToken ct = default)
    {
        CheckDisposed();
        options ??= new AttentionOptions();

        var body = new
        {
            operation = "attention",
            query = TensorToDict(query),
            key = TensorToDict(key),
            value = TensorToDict(value),
            num_heads = options.NumHeads,
            flash = options.Flash,
            precision = options.Precision.ToString().ToLower()
        };

        return await PostAsync<JobResult<Tensor>>("/compute", body, ct);
    }

    // ==================== LLM Inference ====================

    public Task<JobResult<string>> InferenceAsync(string model, string prompt, CancellationToken ct = default)
        => InferenceAsync(model, prompt, new InferenceOptions(), ct);

    public async Task<JobResult<string>> InferenceAsync(
        string model,
        string prompt,
        InferenceOptions options,
        CancellationToken ct = default)
    {
        CheckDisposed();

        var body = new Dictionary<string, object>
        {
            ["operation"] = "inference",
            ["model"] = model,
            ["prompt"] = prompt,
            ["max_tokens"] = options.MaxTokens,
            ["temperature"] = options.Temperature,
            ["top_p"] = options.TopP,
            ["top_k"] = options.TopK
        };

        if (options.Processor.HasValue)
        {
            body["processor"] = options.Processor.Value.ToString().ToLower();
        }

        return await PostAsync<JobResult<string>>("/inference", body, ct);
    }

    public async IAsyncEnumerable<string> InferenceStreamAsync(
        string model,
        string prompt,
        InferenceOptions? options = null,
        [EnumeratorCancellation] CancellationToken ct = default)
    {
        CheckDisposed();
        options ??= new InferenceOptions();

        var body = new Dictionary<string, object>
        {
            ["operation"] = "inference",
            ["model"] = model,
            ["prompt"] = prompt,
            ["max_tokens"] = options.MaxTokens,
            ["temperature"] = options.Temperature,
            ["stream"] = true
        };

        var request = new HttpRequestMessage(HttpMethod.Post, "/inference/stream")
        {
            Content = JsonContent.Create(body, options: _jsonOptions)
        };

        using var response = await _httpClient.SendAsync(
            request,
            HttpCompletionOption.ResponseHeadersRead,
            ct);

        response.EnsureSuccessStatusCode();

        await using var stream = await response.Content.ReadAsStreamAsync(ct);
        using var reader = new StreamReader(stream);

        while (!reader.EndOfStream && !ct.IsCancellationRequested)
        {
            var line = await reader.ReadLineAsync(ct);
            if (line == null) break;

            if (line.StartsWith("data: "))
            {
                var data = line[6..];
                if (data == "[DONE]") yield break;

                try
                {
                    var json = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(data, _jsonOptions);
                    if (json?.TryGetValue("token", out var token) == true)
                    {
                        yield return token.GetString() ?? "";
                    }
                }
                catch (JsonException)
                {
                    // Skip malformed JSON
                }
            }
        }
    }

    // ==================== Model Registry ====================

    public async Task<List<ModelInfo>> ListModelsAsync(
        ModelCategory? category = null,
        CancellationToken ct = default)
    {
        CheckDisposed();

        var url = category.HasValue
            ? $"/models?category={category.Value.ToString().ToLower()}"
            : "/models";

        var response = await GetAsync<JsonElement>(url, ct);
        var models = response.GetProperty("models");

        return models.Deserialize<List<ModelInfo>>(_jsonOptions) ?? new List<ModelInfo>();
    }

    public async Task<ModelInfo> GetModelAsync(string modelId, CancellationToken ct = default)
    {
        CheckDisposed();
        return await GetAsync<ModelInfo>($"/models/{modelId}", ct);
    }

    public async Task<List<ModelInfo>> SearchModelsAsync(string query, CancellationToken ct = default)
    {
        CheckDisposed();
        var response = await GetAsync<JsonElement>($"/models/search?q={Uri.EscapeDataString(query)}", ct);
        var models = response.GetProperty("models");
        return models.Deserialize<List<ModelInfo>>(_jsonOptions) ?? new List<ModelInfo>();
    }

    // ==================== Pricing & Usage ====================

    public async Task<List<PricingInfo>> GetPricingAsync(CancellationToken ct = default)
    {
        CheckDisposed();
        var response = await GetAsync<JsonElement>("/pricing", ct);
        var pricing = response.GetProperty("pricing");
        return pricing.Deserialize<List<PricingInfo>>(_jsonOptions) ?? new List<PricingInfo>();
    }

    public async Task<UsageStats> GetUsageAsync(CancellationToken ct = default)
    {
        CheckDisposed();
        return await GetAsync<UsageStats>("/usage", ct);
    }

    // ==================== Health Check ====================

    public async Task<bool> HealthCheckAsync(CancellationToken ct = default)
    {
        try
        {
            var response = await GetAsync<JsonElement>("/health", ct);
            return response.GetProperty("status").GetString() == "healthy";
        }
        catch
        {
            return false;
        }
    }

    // ==================== Internal Methods ====================

    private async Task<T> GetAsync<T>(string path, CancellationToken ct)
    {
        var response = await _httpClient.GetAsync(path, ct);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)
               ?? throw new SynorException("Failed to deserialize response");
    }

    private async Task<T> PostAsync<T>(string path, object body, CancellationToken ct)
    {
        var response = await _httpClient.PostAsJsonAsync(path, body, _jsonOptions, ct);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)
               ?? throw new SynorException("Failed to deserialize response");
    }

    private static object TensorToDict(Tensor tensor) => new
    {
        shape = tensor.Shape,
        data = tensor.Data,
        dtype = tensor.Dtype.ToString().ToLower()
    };

    private void CheckDisposed()
    {
        ObjectDisposedException.ThrowIf(_disposed, this);
    }

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