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

namespace YMhut.Box.Core.Feedback;

public sealed class FeedbackSubmissionService(HttpClient? httpClient = null) : IFeedbackSubmissionService
{
    public const string Endpoint = "https://mail-smtp.ymhut.cn/";
    public const string ClientSignatureKey = "ymhut-box-feedback-client-v1";
    private readonly HttpClient _httpClient = httpClient ?? new HttpClient { Timeout = TimeSpan.FromSeconds(30) };

    public async Task<FeedbackSubmissionResponse> SubmitAsync(
        FeedbackRequest request,
        FeedbackPackageResult package,
        CancellationToken cancellationToken = default)
    {
        try
        {
            if (!File.Exists(package.PackagePath))
            {
                return new FeedbackSubmissionResponse(false, null, "PACKAGE_MISSING", "本地反馈包不存在。");
            }

            var encrypted = await FeedbackPackageCrypto.EncryptPackageAsync(package.PackagePath, cancellationToken: cancellationToken)
                .ConfigureAwait(false);
            var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(global::System.Globalization.CultureInfo.InvariantCulture);
            var nonce = Guid.NewGuid().ToString("N");
            var feedbackCode = FeedbackCode.Normalize(request.FeedbackCode);
            var payload = JsonSerializer.Serialize(new
            {
                feedbackCode,
                title = request.Title,
                type = request.Type,
                severity = request.Severity,
                contact = request.Contact,
                bodyLength = request.Body?.Length ?? 0,
                packageEncrypted = true,
                encryption = FeedbackPackageCrypto.MagicText,
                packageBytes = encrypted.PackageBytes,
                packageSha256 = encrypted.PackageSha256,
                plainPackageBytes = package.PackageBytes,
                plainPackageSha256 = package.PackageSha256,
                createdAt = package.CreatedAt
            }, new JsonSerializerOptions(JsonSerializerDefaults.Web));
            var signature = Sign(timestamp, nonce, encrypted.PackageSha256, payload);

            using var form = new MultipartFormDataContent();
            form.Add(new StringContent(payload, Encoding.UTF8, "application/json"), "payload");
            form.Add(new StringContent(timestamp, Encoding.UTF8), "timestamp");
            form.Add(new StringContent(nonce, Encoding.UTF8), "nonce");
            form.Add(new StringContent(encrypted.PackageSha256, Encoding.UTF8), "packageSha256");
            form.Add(new StringContent(signature, Encoding.UTF8), "signature");
            await using var stream = File.OpenRead(encrypted.PackagePath);
            using var fileContent = new StreamContent(stream);
            fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
            form.Add(fileContent, "package", Path.GetFileName(encrypted.PackagePath));

            using var response = await _httpClient.PostAsync(Endpoint, form, cancellationToken).ConfigureAwait(false);
            var text = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
            var parsed = ParseResponse(text);
            if (response.IsSuccessStatusCode)
            {
                return parsed;
            }

            return parsed with
            {
                Ok = false,
                ErrorCode = parsed.ErrorCode ?? $"HTTP_{(int)response.StatusCode}",
                Message = parsed.Message ?? response.ReasonPhrase
            };
        }
        catch (Exception exception) when (exception is HttpRequestException or TaskCanceledException or OperationCanceledException)
        {
            return new FeedbackSubmissionResponse(false, null, exception is TaskCanceledException ? "TIMEOUT" : "NETWORK_ERROR", exception.Message);
        }
        catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or InvalidDataException or CryptographicException)
        {
            return new FeedbackSubmissionResponse(false, null, "PACKAGE_ENCRYPT_FAILED", exception.Message);
        }
    }

    public async Task<FeedbackStatusResponse> GetStatusAsync(
        string feedbackCode,
        CancellationToken cancellationToken = default)
    {
        var normalized = FeedbackCode.Normalize(feedbackCode);
        if (!FeedbackCode.IsValid(normalized))
        {
            return new FeedbackStatusResponse(false, normalized, null, null, false, null, null, null, "INVALID_CODE", "Invalid feedback code.");
        }

        try
        {
            var uri = $"{Endpoint}?api=status&code={Uri.EscapeDataString(normalized)}";
            using var response = await _httpClient.GetAsync(uri, cancellationToken).ConfigureAwait(false);
            var text = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
            var parsed = ParseStatusResponse(text);
            if (response.IsSuccessStatusCode)
            {
                return parsed;
            }

            return parsed with
            {
                Ok = false,
                ErrorCode = parsed.ErrorCode ?? $"HTTP_{(int)response.StatusCode}",
                Message = parsed.Message ?? response.ReasonPhrase
            };
        }
        catch (Exception exception) when (exception is HttpRequestException or TaskCanceledException or OperationCanceledException)
        {
            return new FeedbackStatusResponse(false, normalized, null, null, false, null, null, null, exception is TaskCanceledException ? "TIMEOUT" : "NETWORK_ERROR", exception.Message);
        }
    }

    public static string Sign(string timestamp, string nonce, string packageSha256, string payload)
    {
        var material = $"{timestamp}\n{nonce}\n{packageSha256}\n{payload}";
        using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(ClientSignatureKey));
        return Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes(material))).ToLowerInvariant();
    }

    private static FeedbackSubmissionResponse ParseResponse(string text)
    {
        try
        {
            using var document = JsonDocument.Parse(text);
            var root = document.RootElement;
            var ok = root.TryGetProperty("ok", out var okElement) && okElement.ValueKind == JsonValueKind.True;
            var code = root.TryGetProperty("code", out var codeElement) ? codeElement.GetString() : null;
            var error = root.TryGetProperty("error", out var errorElement) ? errorElement.GetString() : null;
            var message = root.TryGetProperty("message", out var messageElement) ? messageElement.GetString() : null;
            var duplicate = root.TryGetProperty("duplicate", out var duplicateElement) && duplicateElement.ValueKind == JsonValueKind.True;
            return new FeedbackSubmissionResponse(ok, code, error, message, duplicate);
        }
        catch (JsonException)
        {
            return new FeedbackSubmissionResponse(false, null, "INVALID_RESPONSE", text);
        }
    }

    private static FeedbackStatusResponse ParseStatusResponse(string text)
    {
        try
        {
            using var document = JsonDocument.Parse(text);
            var root = document.RootElement;
            var ok = root.TryGetProperty("ok", out var okElement) && okElement.ValueKind == JsonValueKind.True;
            var code = root.TryGetProperty("code", out var codeElement) ? codeElement.GetString() : null;
            var status = root.TryGetProperty("status", out var statusElement) ? statusElement.GetString() : null;
            var statusLabel = root.TryGetProperty("statusLabel", out var statusLabelElement) ? statusLabelElement.GetString() : null;
            var statusDetail = root.TryGetProperty("statusDetail", out var statusDetailElement) ? statusDetailElement.GetString() : null;
            var category = root.TryGetProperty("category", out var categoryElement) ? categoryElement.GetString() : null;
            var priority = root.TryGetProperty("priority", out var priorityElement) ? priorityElement.GetString() : null;
            var hasReply = root.TryGetProperty("hasReply", out var hasReplyElement) && hasReplyElement.ValueKind == JsonValueKind.True;
            var reply = root.TryGetProperty("reply", out var replyElement) ? replyElement.GetString() : null;
            var receivedAt = root.TryGetProperty("receivedAt", out var receivedAtElement) ? receivedAtElement.GetString() : null;
            var updatedAt = root.TryGetProperty("updatedAt", out var updatedAtElement) ? updatedAtElement.GetString() : null;
            var mailSent = root.TryGetProperty("mailSent", out var mailSentElement) && mailSentElement.ValueKind == JsonValueKind.True;
            var error = root.TryGetProperty("error", out var errorElement) ? errorElement.GetString() : null;
            var message = root.TryGetProperty("message", out var messageElement) ? messageElement.GetString() : null;
            return new FeedbackStatusResponse(ok, code, status, statusLabel, hasReply, reply, receivedAt, updatedAt, error, message, statusDetail, category, priority, mailSent);
        }
        catch (JsonException)
        {
            return new FeedbackStatusResponse(false, null, null, null, false, null, null, null, "INVALID_RESPONSE", text);
        }
    }
}
