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

namespace Synor.Sdk.Mining;

/// <summary>
/// Synor Mining SDK client for C#.
/// Pool connections, block templates, hashrate stats, and GPU management.
/// </summary>
public class SynorMining : IDisposable
{
    private readonly MiningConfig _config;
    private readonly HttpClient _httpClient;
    private readonly JsonSerializerOptions _jsonOptions;
    private bool _disposed;
    private StratumConnection? _activeConnection;

    public SynorMining(MiningConfig 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.CamelCase, PropertyNameCaseInsensitive = true };
    }

    // ==================== Pool Operations ====================

    public async Task<StratumConnection> ConnectAsync(PoolConfig pool, CancellationToken ct = default)
    {
        var body = new Dictionary<string, object> { ["url"] = pool.Url, ["user"] = pool.User };
        if (pool.Password != null) body["password"] = pool.Password;
        if (pool.Algorithm != null) body["algorithm"] = pool.Algorithm;
        if (pool.Difficulty != null) body["difficulty"] = pool.Difficulty;
        _activeConnection = await PostAsync<StratumConnection>("/pool/connect", body, ct);
        return _activeConnection;
    }

    public async Task DisconnectAsync(CancellationToken ct = default)
    {
        if (_activeConnection == null) return;
        await PostAsync<object>("/pool/disconnect", new { }, ct);
        _activeConnection = null;
    }

    public async Task<StratumConnection> GetConnectionAsync(CancellationToken ct = default)
    {
        _activeConnection = await GetAsync<StratumConnection>("/pool/connection", ct);
        return _activeConnection;
    }

    public async Task<PoolStats> GetPoolStatsAsync(CancellationToken ct = default)
        => await GetAsync<PoolStats>("/pool/stats", ct);

    public bool IsConnected => _activeConnection?.Status == ConnectionStatus.Connected;

    // ==================== Mining Operations ====================

    public async Task<BlockTemplate> GetBlockTemplateAsync(CancellationToken ct = default)
        => await GetAsync<BlockTemplate>("/mining/template", ct);

    public async Task<SubmitResult> SubmitWorkAsync(MinedWork work, CancellationToken ct = default)
        => await PostAsync<SubmitResult>("/mining/submit", work, ct);

    public async Task StartMiningAsync(string? algorithm = null, CancellationToken ct = default)
    {
        var body = algorithm != null ? new { algorithm } : (object)new { };
        await PostAsync<object>("/mining/start", body, ct);
    }

    public async Task StopMiningAsync(CancellationToken ct = default)
        => await PostAsync<object>("/mining/stop", new { }, ct);

    public async Task<bool> IsMiningAsync(CancellationToken ct = default)
    {
        var r = await GetAsync<MiningStatusResponse>("/mining/status", ct);
        return r.Mining;
    }

    // ==================== Stats Operations ====================

    public async Task<Hashrate> GetHashrateAsync(CancellationToken ct = default)
        => await GetAsync<Hashrate>("/stats/hashrate", ct);

    public async Task<MiningStats> GetStatsAsync(CancellationToken ct = default)
        => await GetAsync<MiningStats>("/stats", ct);

    public async Task<Earnings> GetEarningsAsync(TimePeriod? period = null, CancellationToken ct = default)
    {
        var path = period.HasValue ? $"/stats/earnings?period={period.Value.ToString().ToLower()}" : "/stats/earnings";
        return await GetAsync<Earnings>(path, ct);
    }

    public async Task<ShareStats> GetShareStatsAsync(CancellationToken ct = default)
        => await GetAsync<ShareStats>("/stats/shares", ct);

    // ==================== Device Operations ====================

    public async Task<List<MiningDevice>> ListDevicesAsync(CancellationToken ct = default)
    {
        var r = await GetAsync<DevicesResponse>("/devices", ct);
        return r.Devices ?? new List<MiningDevice>();
    }

    public async Task<MiningDevice> GetDeviceAsync(string deviceId, CancellationToken ct = default)
        => await GetAsync<MiningDevice>($"/devices/{Uri.EscapeDataString(deviceId)}", ct);

    public async Task<MiningDevice> SetDeviceConfigAsync(string deviceId, DeviceConfig config, CancellationToken ct = default)
        => await PutAsync<MiningDevice>($"/devices/{Uri.EscapeDataString(deviceId)}/config", config, ct);

    public async Task EnableDeviceAsync(string deviceId, CancellationToken ct = default)
        => await SetDeviceConfigAsync(deviceId, new DeviceConfig(true), ct);

    public async Task DisableDeviceAsync(string deviceId, CancellationToken ct = default)
        => await SetDeviceConfigAsync(deviceId, new DeviceConfig(false), ct);

    // ==================== Worker Operations ====================

    public async Task<List<WorkerInfo>> ListWorkersAsync(CancellationToken ct = default)
    {
        var r = await GetAsync<WorkersResponse>("/workers", ct);
        return r.Workers ?? new List<WorkerInfo>();
    }

    public async Task<WorkerInfo> GetWorkerAsync(string workerId, CancellationToken ct = default)
        => await GetAsync<WorkerInfo>($"/workers/{Uri.EscapeDataString(workerId)}", ct);

    public async Task RemoveWorkerAsync(string workerId, CancellationToken ct = default)
        => await DeleteAsync($"/workers/{Uri.EscapeDataString(workerId)}", ct);

    // ==================== Algorithm Operations ====================

    public async Task<List<MiningAlgorithm>> ListAlgorithmsAsync(CancellationToken ct = default)
    {
        var r = await GetAsync<AlgorithmsResponse>("/algorithms", ct);
        return r.Algorithms ?? new List<MiningAlgorithm>();
    }

    public async Task<MiningAlgorithm> GetAlgorithmAsync(string name, CancellationToken ct = default)
        => await GetAsync<MiningAlgorithm>($"/algorithms/{Uri.EscapeDataString(name)}", ct);

    public async Task SwitchAlgorithmAsync(string algorithm, CancellationToken ct = default)
        => await PostAsync<object>("/algorithms/switch", new { algorithm }, ct);

    public async Task<MiningAlgorithm> GetMostProfitableAsync(CancellationToken ct = default)
        => await GetAsync<MiningAlgorithm>("/algorithms/profitable", ct);

    // ==================== Lifecycle ====================

    public async Task<bool> HealthCheckAsync(CancellationToken ct = default)
    {
        try { var r = await GetAsync<MiningHealthResponse>("/health", ct); return r.Status == "healthy"; }
        catch { return false; }
    }

    public bool IsClosed => _disposed;

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

    // ==================== Private HTTP Methods ====================

    private async Task<T> GetAsync<T>(string path, CancellationToken ct)
        => await ExecuteAsync(async () => {
            var r = await _httpClient.GetAsync(path, ct);
            await EnsureSuccessAsync(r);
            return await r.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)!;
        });

    private async Task<T> PostAsync<T>(string path, object body, CancellationToken ct)
        => await ExecuteAsync(async () => {
            var r = await _httpClient.PostAsJsonAsync(path, body, _jsonOptions, ct);
            await EnsureSuccessAsync(r);
            return await r.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)!;
        });

    private async Task<T> PutAsync<T>(string path, object body, CancellationToken ct)
        => await ExecuteAsync(async () => {
            var r = await _httpClient.PutAsJsonAsync(path, body, _jsonOptions, ct);
            await EnsureSuccessAsync(r);
            return await r.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)!;
        });

    private async Task DeleteAsync(string path, CancellationToken ct)
        => await ExecuteAsync(async () => {
            var r = await _httpClient.DeleteAsync(path, ct);
            await EnsureSuccessAsync(r);
            return true;
        });

    private async Task<T> ExecuteAsync<T>(Func<Task<T>> op)
    {
        Exception? err = null;
        for (int i = 0; i < _config.Retries; i++)
        {
            try { return await op(); }
            catch (Exception ex) { err = ex; if (i < _config.Retries - 1) await Task.Delay(1000 << i); }
        }
        throw err!;
    }

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