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

namespace CashLineERP.Application.Tax;

public sealed class EtaAuthorityClient : IEtaAuthorityClient
{
    public async Task<EtaAuthoritySubmissionReceipt> SubmitDocumentsAsync(
        EtaAuthorityEndpointSelection endpoint,
        EtaAuthorityAccessToken accessToken,
        string submissionReference,
        string payloadText,
        string? signatureText,
        CancellationToken cancellationToken = default)
    {
        using var httpClient = new HttpClient
        {
            Timeout = TimeSpan.FromSeconds(endpoint.TimeoutSeconds)
        };

        using var request = new HttpRequestMessage(HttpMethod.Post, BuildEndpointUri(endpoint));
        request.Headers.Authorization = new AuthenticationHeaderValue(accessToken.TokenType, accessToken.AccessToken);
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        if (!string.IsNullOrWhiteSpace(signatureText))
        {
            request.Headers.TryAddWithoutValidation("X-CashLine-Signature", signatureText);
        }

        var requestBody = BuildSubmissionRequestBody(submissionReference, payloadText);
        request.Content = new StringContent(requestBody, System.Text.Encoding.UTF8, "application/json");

        using var response = await httpClient.SendAsync(request, cancellationToken);
        var responseText = await response.Content.ReadAsStringAsync(cancellationToken);
        if ((int)response.StatusCode is < 200 or >= 300)
        {
            throw new InvalidOperationException($"ETA live submission failed with HTTP {(int)response.StatusCode}: {responseText}");
        }

        using var document = JsonDocument.Parse(responseText);
        var root = document.RootElement;
        return new EtaAuthoritySubmissionReceipt(
            ReadString(root, "submissionUUID") ?? ReadString(root, "submissionUuid") ?? ReadString(root, "uuid"),
            ReadString(root, "status") ?? ReadString(root, "overallStatus"),
            DateTime.UtcNow,
            responseText);
    }

    public async Task<EtaAuthoritySubmissionStatus> GetSubmissionStatusAsync(
        EtaAuthorityEndpointSelection endpoint,
        EtaAuthorityAccessToken accessToken,
        string submissionUuid,
        CancellationToken cancellationToken = default)
    {
        using var httpClient = new HttpClient
        {
            Timeout = TimeSpan.FromSeconds(endpoint.TimeoutSeconds)
        };

        var targetUri = BuildSubmissionStatusUri(endpoint, submissionUuid);
        using var request = new HttpRequestMessage(HttpMethod.Get, targetUri);
        request.Headers.Authorization = new AuthenticationHeaderValue(accessToken.TokenType, accessToken.AccessToken);
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        using var response = await httpClient.SendAsync(request, cancellationToken);
        var responseText = await response.Content.ReadAsStringAsync(cancellationToken);
        if (!response.IsSuccessStatusCode)
        {
            throw new InvalidOperationException($"ETA submission-status request failed with HTTP {(int)response.StatusCode}: {responseText}");
        }

        using var document = JsonDocument.Parse(responseText);
        var root = document.RootElement;

        var documents = new List<EtaAuthoritySubmissionDocumentSummary>();
        if (root.TryGetProperty("documentSummary", out var summaryElement)
            && summaryElement.ValueKind == JsonValueKind.Array)
        {
            foreach (var item in summaryElement.EnumerateArray())
            {
                documents.Add(new EtaAuthoritySubmissionDocumentSummary(
                    ReadString(item, "uuid"),
                    ReadString(item, "internalId"),
                    ReadString(item, "typeName"),
                    ReadString(item, "receiverId"),
                    ReadString(item, "receiverName"),
                    ReadDecimal(item, "total"),
                    ReadString(item, "status"),
                    ReadNestedString(item, "error", "code") ?? ReadString(item, "errorCode"),
                    ReadNestedString(item, "error", "message")
                        ?? ReadString(item, "errorMessage")
                        ?? ReadString(item, "statusReason")
                        ?? ReadFirstArrayString(item, "errors", "message")));
            }
        }

        return new EtaAuthoritySubmissionStatus(
            ReadString(root, "uuid") ?? submissionUuid,
            ReadString(root, "overallStatus"),
            ReadDateTime(root, "dateTimeReceived"),
            ReadNestedString(root, "error", "code") ?? ReadString(root, "errorCode"),
            ReadNestedString(root, "error", "message")
                ?? ReadString(root, "errorMessage")
                ?? ReadString(root, "message")
                ?? documents.Select(document => document.ErrorMessage).FirstOrDefault(message => !string.IsNullOrWhiteSpace(message)),
            documents);
    }

    private static string BuildSubmissionRequestBody(string submissionReference, string payloadText)
    {
        using var document = JsonDocument.Parse(payloadText);
        var root = document.RootElement;

        if (root.ValueKind == JsonValueKind.Object && root.TryGetProperty("documents", out _))
        {
            return payloadText;
        }

        JsonElement documentElement;
        if (root.ValueKind == JsonValueKind.Object
            && root.TryGetProperty("payloadText", out var nestedPayloadElement)
            && nestedPayloadElement.ValueKind == JsonValueKind.String
            && !string.IsNullOrWhiteSpace(nestedPayloadElement.GetString()))
        {
            using var nestedDocument = JsonDocument.Parse(nestedPayloadElement.GetString()!);
            documentElement = nestedDocument.RootElement.Clone();
        }
        else
        {
            documentElement = root.Clone();
        }

        return JsonSerializer.Serialize(new
        {
            submissionReference,
            documents = new[] { documentElement }
        });
    }

    private static Uri BuildEndpointUri(EtaAuthorityEndpointSelection endpoint)
    {
        var baseUrl = endpoint.BaseUrl.TrimEnd('/');
        var relativePath = string.IsNullOrWhiteSpace(endpoint.RelativePath)
            ? string.Empty
            : endpoint.RelativePath.StartsWith('/')
                ? endpoint.RelativePath
                : "/" + endpoint.RelativePath;

        return new Uri($"{baseUrl}{relativePath}", UriKind.Absolute);
    }

    private static Uri BuildSubmissionStatusUri(EtaAuthorityEndpointSelection endpoint, string submissionUuid)
    {
        var endpointUri = BuildEndpointUri(endpoint);
        var baseUrl = endpointUri.GetLeftPart(UriPartial.Authority);
        var relativePath = endpointUri.AbsolutePath;
        var path = relativePath.Replace("{uuid}", Uri.EscapeDataString(submissionUuid), StringComparison.OrdinalIgnoreCase);
        if (!path.Contains(submissionUuid, StringComparison.Ordinal))
        {
            path = $"{path.TrimEnd('/')}/{Uri.EscapeDataString(submissionUuid)}";
        }

        return new Uri($"{baseUrl}{path}", UriKind.Absolute);
    }

    private static string? ReadString(JsonElement element, string propertyName) =>
        element.TryGetProperty(propertyName, out var property)
            ? property.GetString()
            : null;

    private static string? ReadNestedString(JsonElement element, string objectPropertyName, string nestedPropertyName)
    {
        if (!element.TryGetProperty(objectPropertyName, out var property)
            || property.ValueKind != JsonValueKind.Object)
        {
            return null;
        }

        return ReadString(property, nestedPropertyName);
    }

    private static string? ReadFirstArrayString(JsonElement element, string arrayPropertyName, string nestedPropertyName)
    {
        if (!element.TryGetProperty(arrayPropertyName, out var property)
            || property.ValueKind != JsonValueKind.Array)
        {
            return null;
        }

        foreach (var item in property.EnumerateArray())
        {
            var candidate = item.ValueKind == JsonValueKind.Object
                ? ReadString(item, nestedPropertyName)
                : null;

            if (!string.IsNullOrWhiteSpace(candidate))
            {
                return candidate;
            }
        }

        return null;
    }

    private static DateTime? ReadDateTime(JsonElement element, string propertyName)
    {
        if (!element.TryGetProperty(propertyName, out var property))
        {
            return null;
        }

        return property.ValueKind == JsonValueKind.String && property.TryGetDateTime(out var parsed)
            ? parsed
            : null;
    }

    private static decimal? ReadDecimal(JsonElement element, string propertyName)
    {
        if (!element.TryGetProperty(propertyName, out var property))
        {
            return null;
        }

        if (property.ValueKind == JsonValueKind.Number && property.TryGetDecimal(out var numeric))
        {
            return numeric;
        }

        return property.ValueKind == JsonValueKind.String && decimal.TryParse(property.GetString(), out var parsed)
            ? parsed
            : null;
    }
}
