using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Globalization;
using System.Text;
using System.Text.Json;
using GestionaDenuncias.Shared.Models;
using Microsoft.Extensions.Logging;

namespace ApiDenuncias.Services;

public sealed class GestionaDocumentWorkflowService
{
    private const string CircuitTemplateFileDocContentType =
        "application/vnd.gestiona.circuits.template-filedoc+json; version=7";

    private const string FileDocumentContentType =
        "application/vnd.gestiona.file-document+json; version=4";

    private readonly IHttpClientFactory _httpClientFactory;
    private readonly IConfiguration _configuration;
    private readonly ILogger<GestionaDocumentWorkflowService> _logger;

    public GestionaDocumentWorkflowService(
        IHttpClientFactory httpClientFactory,
        IConfiguration configuration,
        ILogger<GestionaDocumentWorkflowService> logger)
    {
        _httpClientFactory = httpClientFactory;
        _configuration = configuration;
        _logger = logger;
    }

    private string GestionaApiBase =>
        (_configuration["Gestiona:ApiBase"] ?? "https://02.g3stiona.com").TrimEnd('/');

    private string GestionaAccessToken =>
        _configuration["Gestiona:AccessToken"]
        ?? throw new InvalidOperationException("Falta Gestiona:AccessToken en appsettings.");

    private string DocumentMetadataLanguage =>
        _configuration["Gestiona:DocumentMetadataLanguage"] ?? "es";

    private string DocumentMetadataType =>
        _configuration["Gestiona:DocumentMetadataType"]
        ?? throw new InvalidOperationException("Falta Gestiona:DocumentMetadataType para informar el tipo documental.");

    private string DocumentMetadataSubtype =>
        _configuration["Gestiona:DocumentMetadataSubtype"]
        ?? throw new InvalidOperationException("Falta Gestiona:DocumentMetadataSubtype para informar el subtipo documental.");

    public async Task<string> UploadDocumentAndReturnUrlAsync(string fileUrl, byte[] contentBytes, string fileName)
    {
        var fileUrlAbs = EnsureAbsoluteGestionaUrl(fileUrl, GestionaApiBase);
        var documentsTargetUrl = ResolveDocumentsContainerUrl(fileUrlAbs);
        var uploadUri = await CreateUploadAsync(contentBytes, fileName);

        var metaPayload = new
        {
            type = "DIGITAL",
            name = fileName,
            description = "Documento de denuncia",
            elaboration_state = "EE01",
            metadata_language = DocumentMetadataLanguage,
            metadata_type = DocumentMetadataType,
            metadata_subtype = DocumentMetadataSubtype,
            links = new[] { new { rel = "content", href = uploadUri } }
        };

        var metaJson = JsonSerializer.Serialize(metaPayload);

        using var metaReq = new HttpRequestMessage(HttpMethod.Post, documentsTargetUrl);
        metaReq.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", GestionaAccessToken);
        metaReq.Headers.TryAddWithoutValidation("Prefer", "return=minimal");
        metaReq.Headers.Accept.Clear();

        metaReq.Content = new StringContent(metaJson, Encoding.UTF8);
        metaReq.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(FileDocumentContentType);

        using var metaResp = await CreateRawHttp().SendAsync(metaReq);
        LogDeprecatedHeaders(metaResp, "POST documento Gestiona");
        var body = await metaResp.Content.ReadAsStringAsync();

        if (!metaResp.IsSuccessStatusCode)
        {
            throw new InvalidOperationException(
                $"UploadDocumentAndReturnUrlAsync: {(int)metaResp.StatusCode} {metaResp.StatusCode}\n{body}");
        }

        if (!string.IsNullOrWhiteSpace(body))
        {
            try
            {
                using var doc = JsonDocument.Parse(body);
                if (doc.RootElement.TryGetProperty("links", out var links) && links.ValueKind == JsonValueKind.Array)
                {
                    foreach (var link in links.EnumerateArray())
                    {
                        var rel = link.TryGetProperty("rel", out var pRel) ? pRel.GetString() : null;
                        var href = link.TryGetProperty("href", out var pHref) ? pHref.GetString() : null;
                        if (string.Equals(rel, "self", StringComparison.OrdinalIgnoreCase) &&
                            !string.IsNullOrWhiteSpace(href) &&
                            href!.Contains("/documents/", StringComparison.OrdinalIgnoreCase))
                        {
                            return href;
                        }
                    }
                }
            }
            catch
            {
                // Si Gestiona devuelve cuerpo vacío por Prefer:return=minimal, usamos Location del documento.
            }
        }

        var location = metaResp.Headers.Location?.ToString();
        if (!string.IsNullOrWhiteSpace(location))
        {
            return location!;
        }

        throw new InvalidOperationException("No se pudo obtener la URL del documento creado en Gestiona.");
    }

    public async Task TramitarDocumentoAsync(
        string documentUrl,
        string assignedGroupCode,
        int? complaintId = null,
        bool isUpdate = false,
        string? updateSource = null)
    {
        var docUrlAbs = EnsureAbsoluteGestionaUrl(documentUrl, GestionaApiBase);
        var operationLabel = ComplaintUpdateSources.IsReceiver(updateSource)
            ? $"comunicacion OAAF grupo {NormalizeGroupCode(assignedGroupCode)}"
            : isUpdate
                ? $"actualizacion grupo {NormalizeGroupCode(assignedGroupCode)}"
                : "nueva denuncia";
        var (templateHref, payload) = await ResolveCircuitTemplatePayloadAsync(
            docUrlAbs,
            isUpdate,
            assignedGroupCode,
            updateSource);
        var (success, statusCode, body) = await TryPostCircuitAsync(docUrlAbs, payload);
        if (success)
        {
            _logger.LogInformation(
                "Documento {DocumentUrl} enviado a circuito con plantilla {TemplateHref}. Denuncia={ComplaintId}. Tipo={OperationType}.",
                docUrlAbs,
                templateHref,
                complaintId,
                operationLabel);
            return;
        }

        _logger.LogError(
            "Fallo al tramitar documento {DocumentUrl} con plantilla configurada ({TemplateHref}). Status: {StatusCode}. Body: {Body}",
            docUrlAbs,
            templateHref,
            (int)statusCode,
            body);

        throw new InvalidOperationException(
            $"TramitarDocumentoAsync: {(int)statusCode} {statusCode}\n{body}");
    }

    private async Task<(string TemplateHref, string Payload)> ResolveCircuitTemplatePayloadAsync(
        string documentUrl,
        bool isUpdate,
        string? assignedGroupCode,
        string? updateSource)
    {
        var templatesUrl = $"{documentUrl.TrimEnd('/')}/circuit/templates";
        var templates = await GetCircuitTemplatesAsync(templatesUrl);
        if (templates.Count == 0)
        {
            throw new InvalidOperationException("Gestiona no ha devuelto plantillas de circuito para el documento.");
        }

        var selection = GetOperationCircuitTemplateSelection(isUpdate, assignedGroupCode, updateSource);
        if (!string.IsNullOrWhiteSpace(selection.TemplateName))
        {
            var resolved = await TryResolveCircuitTemplatePayloadByNameAsync(templates, selection.TemplateName);
            if (resolved is not null)
            {
                return resolved.Value;
            }

            if (selection.Required)
            {
                throw new InvalidOperationException(
                    $"No se ha encontrado la plantilla de circuito '{selection.TemplateName}' para {selection.OperationLabel}.");
            }
        }

        var configuredName = _configuration["Gestiona:CircuitTemplateName"];
        var configuredResolved = await TryResolveCircuitTemplatePayloadByNameAsync(templates, configuredName);
        if (configuredResolved is not null)
        {
            return configuredResolved.Value;
        }

        var signerTitle = _configuration["Gestiona:CircuitSignerStampTitle"];
        if (!string.IsNullOrWhiteSpace(signerTitle))
        {
            foreach (var template in templates)
            {
                var href = GetRequiredSelfHref(template);
                var payload = await GetCircuitTemplatePayloadAsync(href);
                if (payload.Contains(signerTitle, StringComparison.OrdinalIgnoreCase))
                {
                    return (href, payload);
                }
            }
        }

        if (templates.Count == 1)
        {
            var href = GetRequiredSelfHref(templates[0]);
            return (href, await GetCircuitTemplatePayloadAsync(href));
        }

        throw new InvalidOperationException(
            "No se puede seleccionar de forma inequivoca la plantilla de circuito. Configura Gestiona:CircuitTemplateName o Gestiona:CircuitSignerStampTitle.");
    }

    private CircuitTemplateSelection GetOperationCircuitTemplateSelection(
        bool isUpdate,
        string? assignedGroupCode,
        string? updateSource)
    {
        if (!isUpdate)
        {
            return new CircuitTemplateSelection(
                _configuration["Gestiona:CircuitNewComplaintTemplateName"],
                Required: true,
                OperationLabel: "nueva denuncia");
        }

        if (ComplaintUpdateSources.IsReceiver(updateSource))
        {
            return NormalizeGroupCode(assignedGroupCode) switch
            {
                "510" => new CircuitTemplateSelection(
                    _configuration["Gestiona:CircuitCommunicationSdiTemplateName"],
                    Required: true,
                    OperationLabel: "comunicacion SDI a denunciante"),

                "600" => new CircuitTemplateSelection(
                    _configuration["Gestiona:CircuitCommunicationSajTemplateName"],
                    Required: true,
                    OperationLabel: "comunicacion SAJ a denunciante"),

                _ => throw new InvalidOperationException(
                    "Las comunicaciones de la OAAF solo pueden tramitarse con los grupos 510 o 600.")
            };
        }

        var defaultUpdateTemplateName = FirstConfigured(
            _configuration["Gestiona:CircuitTemplateName"],
            _configuration["Gestiona:CircuitUpdateTemplateName"]);

        return NormalizeGroupCode(assignedGroupCode) switch
        {
            "510" => new CircuitTemplateSelection(
                FirstConfigured(
                    _configuration["Gestiona:CircuitUpdateSdiTemplateName"],
                    _configuration["Gestiona:CircuitUpdateTemplateNameSdi"]),
                Required: true,
                OperationLabel: "actualizacion de denuncia SDI"),

            "600" => new CircuitTemplateSelection(
                FirstConfigured(
                    _configuration["Gestiona:CircuitUpdateSajTemplateName"],
                    _configuration["Gestiona:CircuitUpdateTemplateNameSaj"]),
                Required: true,
                OperationLabel: "actualizacion de denuncia SAJ"),

            _ => new CircuitTemplateSelection(
                defaultUpdateTemplateName,
                Required: !string.IsNullOrWhiteSpace(defaultUpdateTemplateName),
                OperationLabel: "actualizacion de denuncia")
        };
    }

    private static string? FirstConfigured(params string?[] values)
        => values.FirstOrDefault(value => !string.IsNullOrWhiteSpace(value));

    private static string NormalizeGroupCode(string? assignedGroupCode)
    {
        if (string.IsNullOrWhiteSpace(assignedGroupCode))
        {
            return string.Empty;
        }

        var digits = new string(assignedGroupCode.Where(char.IsDigit).ToArray());
        return string.IsNullOrWhiteSpace(digits) ? assignedGroupCode.Trim() : digits;
    }

    private sealed record CircuitTemplateSelection(string? TemplateName, bool Required, string OperationLabel);

    private async Task<(string TemplateHref, string Payload)?> TryResolveCircuitTemplatePayloadByNameAsync(
        List<JsonElement> templates,
        string? templateName)
    {
        var configuredName = NormalizeKey(templateName);
        if (string.IsNullOrWhiteSpace(configuredName))
        {
            return null;
        }

        foreach (var template in templates)
        {
            if (NormalizeKey(GetJsonString(template, "name")).Contains(configuredName, StringComparison.Ordinal) ||
                NormalizeKey(GetJsonString(template, "title")).Contains(configuredName, StringComparison.Ordinal))
            {
                var href = GetRequiredSelfHref(template);
                return (href, await GetCircuitTemplatePayloadAsync(href));
            }
        }

        return null;
    }

    private async Task<List<JsonElement>> GetCircuitTemplatesAsync(string templatesUrl)
    {
        using var req = new HttpRequestMessage(HttpMethod.Get, templatesUrl);
        req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", GestionaAccessToken);
        req.Headers.Accept.Add(
            MediaTypeWithQualityHeaderValue.Parse("application/vnd.gestiona.circuits.templates-filedoc-page+json"));

        using var resp = await CreateRawHttp().SendAsync(req);
        LogDeprecatedHeaders(resp, "GET plantillas circuito Gestiona");
        var body = await resp.Content.ReadAsStringAsync();
        if (!resp.IsSuccessStatusCode)
        {
            throw new InvalidOperationException(
                $"GetCircuitTemplatesAsync: {(int)resp.StatusCode} {resp.StatusCode}\n{body}");
        }

        using var doc = JsonDocument.Parse(body);
        var source = doc.RootElement.TryGetProperty("content", out var content) &&
                     content.ValueKind == JsonValueKind.Array
            ? content
            : doc.RootElement;

        if (source.ValueKind != JsonValueKind.Array)
        {
            return [];
        }

        return source.EnumerateArray().Select(item => item.Clone()).ToList();
    }

    private async Task<string> GetCircuitTemplatePayloadAsync(string templateHref)
    {
        using var req = new HttpRequestMessage(HttpMethod.Get, templateHref);
        req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", GestionaAccessToken);
        req.Headers.Accept.Add(
            MediaTypeWithQualityHeaderValue.Parse(CircuitTemplateFileDocContentType));

        using var resp = await CreateRawHttp().SendAsync(req);
        LogDeprecatedHeaders(resp, "GET plantilla circuito Gestiona");
        var body = await resp.Content.ReadAsStringAsync();
        if (!resp.IsSuccessStatusCode)
        {
            throw new InvalidOperationException(
                $"GetCircuitTemplatePayloadAsync: {(int)resp.StatusCode} {resp.StatusCode}\n{body}");
        }

        if (string.IsNullOrWhiteSpace(body))
        {
            throw new InvalidOperationException("Gestiona ha devuelto vacia la plantilla de circuito configurada.");
        }

        return body;
    }

    private async Task<(bool Success, System.Net.HttpStatusCode StatusCode, string Body)> TryPostCircuitAsync(
        string documentUrl,
        string payload)
    {
        using var req = new HttpRequestMessage(HttpMethod.Post, $"{documentUrl.TrimEnd('/')}/circuit");
        req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", GestionaAccessToken);
        req.Headers.TryAddWithoutValidation("Accept", "application/json");
        req.Content = new StringContent(payload, Encoding.UTF8);
        req.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(CircuitTemplateFileDocContentType);

        using var resp = await CreateRawHttp().SendAsync(req);
        LogDeprecatedHeaders(resp, "POST circuito Gestiona");
        var body = await resp.Content.ReadAsStringAsync();
        return (resp.IsSuccessStatusCode, resp.StatusCode, body);
    }

    private HttpClient CreateRawHttp() => _httpClientFactory.CreateClient();

    private async Task<string> CreateUploadAsync(byte[] contentBytes, string fileName)
    {
        using var createReq = new HttpRequestMessage(HttpMethod.Post, $"{GestionaApiBase}/rest/uploads");
        createReq.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", GestionaAccessToken);
        createReq.Headers.TryAddWithoutValidation("Prefer", "return=minimal");
        createReq.Headers.Accept.Add(
            MediaTypeWithQualityHeaderValue.Parse("application/vnd.gestiona.upload+json"));

        using var createResp = await CreateRawHttp().SendAsync(createReq);
        LogDeprecatedHeaders(createResp, "POST upload Gestiona");
        var createBody = await createResp.Content.ReadAsStringAsync();
        if (!createResp.IsSuccessStatusCode)
        {
            throw new InvalidOperationException(
                $"CreateUploadAsync (POST): {(int)createResp.StatusCode} {createResp.StatusCode}\n{createBody}");
        }

        var uploadUri = createResp.Headers.Location?.ToString()
                        ?? throw new InvalidOperationException("No se devolvio Location en /rest/uploads");

        using var putReq = new HttpRequestMessage(HttpMethod.Put, uploadUri);
        putReq.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", GestionaAccessToken);
        putReq.Headers.TryAddWithoutValidation("X-Gestiona-Upload-MD5", GetMd5Hex(contentBytes));
        putReq.Headers.TryAddWithoutValidation("Slug", fileName);
        putReq.Headers.TryAddWithoutValidation("Prefer", "return=minimal");
        putReq.Headers.Accept.Add(
            MediaTypeWithQualityHeaderValue.Parse("application/vnd.gestiona.upload+json"));
        putReq.Content = new ByteArrayContent(contentBytes);
        putReq.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");

        using var putResp = await CreateRawHttp().SendAsync(putReq);
        LogDeprecatedHeaders(putResp, "PUT upload Gestiona");
        var infoJson = await putResp.Content.ReadAsStringAsync();
        if (!putResp.IsSuccessStatusCode)
        {
            throw new InvalidOperationException(
                $"CreateUploadAsync (PUT): {(int)putResp.StatusCode} {putResp.StatusCode}\n{infoJson}");
        }

        if (!string.IsNullOrWhiteSpace(infoJson))
        {
            using var infoDoc = JsonDocument.Parse(infoJson);
            var status = infoDoc.RootElement.TryGetProperty("status", out var pStatus) ? pStatus.GetString() : "READY";
            if (!string.Equals(status, "READY", StringComparison.OrdinalIgnoreCase))
            {
                throw new InvalidOperationException($"Upload no READY: {status}");
            }
        }

        return uploadUri;
    }

    private static string ResolveDocumentsContainerUrl(string url)
    {
        var normalized = url.TrimEnd('/');
        return normalized.Contains("/documents-and-folders", StringComparison.OrdinalIgnoreCase)
            ? normalized
            : $"{normalized}/documents-and-folders";
    }

    private static string EnsureAbsoluteGestionaUrl(string url, string apiBase)
    {
        if (string.IsNullOrWhiteSpace(url))
        {
            throw new InvalidOperationException("URL vacia.");
        }

        if (Uri.TryCreate(url, UriKind.Absolute, out _))
        {
            return url;
        }

        if (!url.StartsWith('/'))
        {
            url = "/" + url;
        }

        return apiBase.TrimEnd('/') + url;
    }

    private static string GetMd5Hex(byte[] bytes)
    {
        using var md5 = MD5.Create();
        var hash = md5.ComputeHash(bytes);
        return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
    }

    private static string GetRequiredSelfHref(JsonElement item)
    {
        return GetLinkHref(item, "self")
               ?? throw new InvalidOperationException("La plantilla de circuito no contiene link 'self'.");
    }

    private static string? GetLinkHref(JsonElement item, string rel)
    {
        if (!item.TryGetProperty("links", out var links) || links.ValueKind != JsonValueKind.Array)
        {
            return null;
        }

        foreach (var link in links.EnumerateArray())
        {
            if (link.TryGetProperty("rel", out var relProp) &&
                string.Equals(relProp.GetString(), rel, StringComparison.OrdinalIgnoreCase) &&
                link.TryGetProperty("href", out var hrefProp) &&
                hrefProp.ValueKind == JsonValueKind.String)
            {
                return hrefProp.GetString();
            }
        }

        return null;
    }

    private static string? GetJsonString(JsonElement item, string propertyName)
    {
        return item.TryGetProperty(propertyName, out var property) &&
               property.ValueKind == JsonValueKind.String
            ? property.GetString()
            : null;
    }

    private static string NormalizeKey(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return string.Empty;
        }

        var normalized = value.Normalize(NormalizationForm.FormD);
        var builder = new StringBuilder(normalized.Length);

        foreach (var character in normalized)
        {
            var category = CharUnicodeInfo.GetUnicodeCategory(character);
            if (category == UnicodeCategory.NonSpacingMark)
            {
                continue;
            }

            builder.Append(char.IsLetterOrDigit(character) ? char.ToUpperInvariant(character) : ' ');
        }

        return string.Join(' ', builder.ToString().Split(' ', StringSplitOptions.RemoveEmptyEntries));
    }

    private void LogDeprecatedHeaders(HttpResponseMessage response, string operation)
    {
        if (response.Headers.TryGetValues("X-Gestiona-Deprecated", out var deprecated))
        {
            _logger.LogWarning(
                "Gestiona devolvio X-Gestiona-Deprecated en {Operation}: {Deprecated}",
                operation,
                string.Join(" | ", deprecated));
        }
    }

}
