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

namespace Synor.Sdk.Storage;

/// <summary>
/// Synor Storage SDK client for C#.
///
/// Provides decentralized storage operations including upload, download,
/// pinning, and directory management.
/// </summary>
/// <example>
/// <code>
/// var storage = new SynorStorage(new StorageConfig { ApiKey = "your-api-key" });
///
/// // Upload a file
/// var data = File.ReadAllBytes("document.pdf");
/// var result = await storage.UploadAsync(data, new UploadOptions { Name = "document.pdf" });
/// Console.WriteLine($"CID: {result.Cid}");
///
/// // Download a file
/// var downloaded = await storage.DownloadAsync(result.Cid);
/// File.WriteAllBytes("downloaded.pdf", downloaded);
///
/// storage.Dispose();
/// </code>
/// </example>
public class SynorStorage : IDisposable
{
    private readonly StorageConfig _config;
    private readonly HttpClient _httpClient;
    private readonly JsonSerializerOptions _jsonOptions;
    private bool _disposed;

    public SynorStorage(StorageConfig config)
    {
        _config = config;
        _httpClient = new HttpClient
        {
            BaseAddress = new Uri(config.Endpoint),
            Timeout = config.Timeout
        };
        _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {config.ApiKey}");

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

    /// <summary>
    /// Upload data to storage.
    /// </summary>
    public async Task<UploadResponse> UploadAsync(
        byte[] data,
        UploadOptions? options = null,
        CancellationToken cancellationToken = default)
    {
        options ??= new UploadOptions();

        using var content = new MultipartFormDataContent();
        var fileContent = new ByteArrayContent(data);

        if (!string.IsNullOrEmpty(options.ContentType))
        {
            fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse(options.ContentType);
        }

        content.Add(fileContent, "file", options.Name ?? "file");

        if (options.Metadata != null)
        {
            foreach (var (key, value) in options.Metadata)
            {
                content.Add(new StringContent(value), $"metadata[{key}]");
            }
        }

        content.Add(new StringContent(options.Pin.ToString().ToLower()), "pin");
        content.Add(new StringContent(options.WrapWithDirectory.ToString().ToLower()), "wrap_with_directory");

        return await ExecuteWithRetryAsync(async () =>
        {
            var response = await _httpClient.PostAsync("/upload", content, cancellationToken);
            await EnsureSuccessAsync(response);
            return await response.Content.ReadFromJsonAsync<UploadResponse>(_jsonOptions, cancellationToken)
                ?? throw new StorageException("Invalid response");
        });
    }

    /// <summary>
    /// Upload a stream to storage.
    /// </summary>
    public async Task<UploadResponse> UploadStreamAsync(
        Stream stream,
        UploadOptions? options = null,
        CancellationToken cancellationToken = default)
    {
        options ??= new UploadOptions();

        using var content = new MultipartFormDataContent();
        var streamContent = new StreamContent(stream);

        if (!string.IsNullOrEmpty(options.ContentType))
        {
            streamContent.Headers.ContentType = MediaTypeHeaderValue.Parse(options.ContentType);
        }

        content.Add(streamContent, "file", options.Name ?? "file");
        content.Add(new StringContent(options.Pin.ToString().ToLower()), "pin");

        return await ExecuteWithRetryAsync(async () =>
        {
            var response = await _httpClient.PostAsync("/upload", content, cancellationToken);
            await EnsureSuccessAsync(response);
            return await response.Content.ReadFromJsonAsync<UploadResponse>(_jsonOptions, cancellationToken)
                ?? throw new StorageException("Invalid response");
        });
    }

    /// <summary>
    /// Upload a directory of files.
    /// </summary>
    public async Task<UploadResponse> UploadDirectoryAsync(
        IEnumerable<FileEntry> files,
        string? directoryName = null,
        CancellationToken cancellationToken = default)
    {
        using var content = new MultipartFormDataContent();

        foreach (var file in files)
        {
            var fileContent = new ByteArrayContent(file.Content);
            if (!string.IsNullOrEmpty(file.ContentType))
            {
                fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse(file.ContentType);
            }
            content.Add(fileContent, "files", file.Path);
        }

        if (!string.IsNullOrEmpty(directoryName))
        {
            content.Add(new StringContent(directoryName), "directory_name");
        }

        return await ExecuteWithRetryAsync(async () =>
        {
            var response = await _httpClient.PostAsync("/upload/directory", content, cancellationToken);
            await EnsureSuccessAsync(response);
            return await response.Content.ReadFromJsonAsync<UploadResponse>(_jsonOptions, cancellationToken)
                ?? throw new StorageException("Invalid response");
        });
    }

    /// <summary>
    /// Download content by CID.
    /// </summary>
    public async Task<byte[]> DownloadAsync(
        string cid,
        CancellationToken cancellationToken = default)
    {
        return await ExecuteWithRetryAsync(async () =>
        {
            var response = await _httpClient.GetAsync($"/download/{cid}", cancellationToken);
            await EnsureSuccessAsync(response);
            return await response.Content.ReadAsByteArrayAsync(cancellationToken);
        });
    }

    /// <summary>
    /// Download content as a stream.
    /// </summary>
    public async Task<Stream> DownloadStreamAsync(
        string cid,
        CancellationToken cancellationToken = default)
    {
        var response = await _httpClient.GetAsync(
            $"/download/{cid}",
            HttpCompletionOption.ResponseHeadersRead,
            cancellationToken);
        await EnsureSuccessAsync(response);
        return await response.Content.ReadAsStreamAsync(cancellationToken);
    }

    /// <summary>
    /// Get gateway URL for a CID.
    /// </summary>
    public string GetGatewayUrl(string cid, string? path = null)
    {
        var url = $"{_config.Gateway}/ipfs/{cid}";
        if (!string.IsNullOrEmpty(path))
        {
            url += $"/{path.TrimStart('/')}";
        }
        return url;
    }

    /// <summary>
    /// Pin content by CID.
    /// </summary>
    public async Task<Pin> PinAsync(
        PinRequest request,
        CancellationToken cancellationToken = default)
    {
        return await PostAsync<Pin>("/pins", request, cancellationToken);
    }

    /// <summary>
    /// Unpin content.
    /// </summary>
    public async Task UnpinAsync(
        string cid,
        CancellationToken cancellationToken = default)
    {
        await ExecuteWithRetryAsync(async () =>
        {
            var response = await _httpClient.DeleteAsync($"/pins/{cid}", cancellationToken);
            await EnsureSuccessAsync(response);
            return true;
        });
    }

    /// <summary>
    /// Get pin status.
    /// </summary>
    public async Task<Pin> GetPinStatusAsync(
        string cid,
        CancellationToken cancellationToken = default)
    {
        return await GetAsync<Pin>($"/pins/{cid}", cancellationToken);
    }

    /// <summary>
    /// List pins.
    /// </summary>
    public async Task<List<Pin>> ListPinsAsync(
        PinStatus? status = null,
        int limit = 20,
        int offset = 0,
        CancellationToken cancellationToken = default)
    {
        var query = $"/pins?limit={limit}&offset={offset}";
        if (status.HasValue)
        {
            query += $"&status={status.Value.ToString().ToLower()}";
        }
        return await GetAsync<List<Pin>>(query, cancellationToken);
    }

    /// <summary>
    /// Create a CAR file from entries.
    /// </summary>
    public async Task<CarFile> CreateCarAsync(
        IEnumerable<FileEntry> entries,
        CancellationToken cancellationToken = default)
    {
        var request = entries.Select(e => new
        {
            path = e.Path,
            content = Convert.ToBase64String(e.Content),
            content_type = e.ContentType
        }).ToList();

        return await PostAsync<CarFile>("/car/create", request, cancellationToken);
    }

    /// <summary>
    /// Import a CAR file.
    /// </summary>
    public async Task<List<string>> ImportCarAsync(
        byte[] carData,
        CancellationToken cancellationToken = default)
    {
        using var content = new ByteArrayContent(carData);
        content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/vnd.ipld.car");

        return await ExecuteWithRetryAsync(async () =>
        {
            var response = await _httpClient.PostAsync("/car/import", content, cancellationToken);
            await EnsureSuccessAsync(response);
            var result = await response.Content.ReadFromJsonAsync<JsonElement>(_jsonOptions, cancellationToken);
            return result.GetProperty("cids").EnumerateArray()
                .Select(e => e.GetString()!)
                .ToList();
        });
    }

    /// <summary>
    /// Export content as a CAR file.
    /// </summary>
    public async Task<byte[]> ExportCarAsync(
        string cid,
        CancellationToken cancellationToken = default)
    {
        return await ExecuteWithRetryAsync(async () =>
        {
            var response = await _httpClient.GetAsync($"/car/export/{cid}", cancellationToken);
            await EnsureSuccessAsync(response);
            return await response.Content.ReadAsByteArrayAsync(cancellationToken);
        });
    }

    /// <summary>
    /// List directory contents.
    /// </summary>
    public async Task<List<DirectoryEntry>> ListDirectoryAsync(
        string cid,
        CancellationToken cancellationToken = default)
    {
        return await GetAsync<List<DirectoryEntry>>($"/directory/{cid}", cancellationToken);
    }

    /// <summary>
    /// Get storage statistics.
    /// </summary>
    public async Task<StorageStats> GetStatsAsync(CancellationToken cancellationToken = default)
    {
        return await GetAsync<StorageStats>("/stats", cancellationToken);
    }

    private async Task<T> GetAsync<T>(string path, CancellationToken cancellationToken)
    {
        return await ExecuteWithRetryAsync(async () =>
        {
            var response = await _httpClient.GetAsync(path, cancellationToken);
            await EnsureSuccessAsync(response);
            return await response.Content.ReadFromJsonAsync<T>(_jsonOptions, cancellationToken)
                ?? throw new StorageException("Invalid response");
        });
    }

    private async Task<T> PostAsync<T>(string path, object body, CancellationToken cancellationToken)
    {
        return await ExecuteWithRetryAsync(async () =>
        {
            var response = await _httpClient.PostAsJsonAsync(path, body, _jsonOptions, cancellationToken);
            await EnsureSuccessAsync(response);
            return await response.Content.ReadFromJsonAsync<T>(_jsonOptions, cancellationToken)
                ?? throw new StorageException("Invalid response");
        });
    }

    private async Task EnsureSuccessAsync(HttpResponseMessage response)
    {
        if (!response.IsSuccessStatusCode)
        {
            var content = await response.Content.ReadAsStringAsync();
            throw new StorageException(
                $"HTTP error: {content}",
                statusCode: (int)response.StatusCode);
        }
    }

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

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

        throw lastException ?? new StorageException("Unknown error after retries");
    }

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