using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.Extensions.Logging;

namespace ApiDenuncias.Services;

public sealed class GestionaDocumentWorkflowService
{
    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? PreferredCircuitTemplateName =>
        _configuration["Gestiona:PreferredCircuitTemplateName"];

    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 = "ES",
            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.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        metaReq.Content = new StringContent(metaJson, Encoding.UTF8);
        metaReq.Content.Headers.ContentType =
            MediaTypeHeaderValue.Parse("application/vnd.gestiona.file-document+json; version=4");

        using var metaResp = await CreateRawHttp().SendAsync(metaReq);
        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 no devuelve un JSON valido, intentamos con la cabecera Location.
            }
        }

        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 assignedGroupHref, int? complaintId = null)
    {
        var docUrlAbs = EnsureAbsoluteGestionaUrl(documentUrl, GestionaApiBase);
        var payload = BuildConfiguredCircuitPayload(docUrlAbs, assignedGroupHref, complaintId);
        string? templateNameForLog = "configurada";
        string? templateHrefForLog = GetConfiguredTemplateHref(docUrlAbs);

        if (payload is null)
        {
            throw new InvalidOperationException(
                "Faltan Gestiona:CircuitTemplateId o Gestiona:CircuitSignerStampHref. No se listan plantillas para evitar campos deprecated.");
        }

        var json = payload.ToJsonString(new JsonSerializerOptions(JsonSerializerDefaults.Web));

        using var req = new HttpRequestMessage(HttpMethod.Post, $"{docUrlAbs.TrimEnd('/')}/circuit");
        req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", GestionaAccessToken);
        req.Headers.TryAddWithoutValidation("Accept", "application/json");
        req.Content = new StringContent(json, Encoding.UTF8);
        req.Content.Headers.ContentType =
            MediaTypeHeaderValue.Parse("application/vnd.gestiona.circuits.template-filedoc+json; version=7");

        using var resp = await CreateRawHttp().SendAsync(req);
        var body = await resp.Content.ReadAsStringAsync();
        if (!resp.IsSuccessStatusCode)
        {
            _logger.LogError(
                "Fallo al tramitar documento {DocumentUrl} con plantilla {TemplateName} ({TemplateHref}). Status: {StatusCode}. Body: {Body}",
                docUrlAbs,
                templateNameForLog,
                templateHrefForLog,
                (int)resp.StatusCode,
                body);

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

        _logger.LogInformation(
            "Documento {DocumentUrl} enviado a circuito {TemplateName} ({TemplateHref}) para denuncia {ComplaintId}.",
            docUrlAbs,
            templateNameForLog,
            templateHrefForLog,
            complaintId);
    }

    private JsonObject? BuildConfiguredCircuitPayload(string documentUrl, string assignedGroupHref, int? complaintId)
    {
        _ = assignedGroupHref;
        _ = complaintId;

        var templateHref = GetConfiguredTemplateHref(documentUrl);
        var signerHref = _configuration["Gestiona:CircuitSignerStampHref"];
        if (string.IsNullOrWhiteSpace(templateHref) || string.IsNullOrWhiteSpace(signerHref))
        {
            return null;
        }

        var payload = new JsonObject
        {
            ["block_edit"] = true,
            ["send_alerts"] = true,
            ["version"] = _configuration["Gestiona:CircuitVersion"] ?? "2",
            ["signers"] = new JsonArray
            {
                JsonSerializer.SerializeToNode(new
                {
                    rel = "signer-stamp",
                    href = signerHref,
                    title = _configuration["Gestiona:CircuitSignerStampTitle"] ?? "oaaf-complaints-tramit"
                })
            },
            ["links"] = new JsonArray
            {
                JsonSerializer.SerializeToNode(new { rel = "self", href = templateHref })
            }
        };

        var recipientGroupHref = _configuration["Gestiona:CircuitRecipientGroupHref"];
        if (!string.IsNullOrWhiteSpace(recipientGroupHref))
        {
            payload["recipients"] = new JsonArray
            {
                JsonSerializer.SerializeToNode(new
                {
                    rel = "group",
                    href = recipientGroupHref
                })
            };
        }

        return payload;
    }

    private string? GetConfiguredTemplateHref(string documentUrl)
    {
        var templateId = _configuration["Gestiona:CircuitTemplateId"];
        return string.IsNullOrWhiteSpace(templateId)
            ? null
            : $"{documentUrl.TrimEnd('/')}/circuit/templates/{templateId.Trim()}";
    }

    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.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        using var createResp = await CreateRawHttp().SendAsync(createReq);
        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.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        putReq.Content = new ByteArrayContent(contentBytes);
        putReq.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");

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

        using var infoDoc = JsonDocument.Parse(infoJson);
        var status = infoDoc.RootElement.TryGetProperty("status", out var pStatus) ? pStatus.GetString() : null;
        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 async Task<CircuitTemplateCandidate> ObtenerTemplateCircuitoFirmaAsync(string documentUrl)
    {
        using var req = new HttpRequestMessage(HttpMethod.Get, $"{documentUrl.TrimEnd('/')}/circuit/templates");
        req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", GestionaAccessToken);
        req.Headers.TryAddWithoutValidation("Accept", "application/vnd.gestiona.circuits.templates-filedoc-page");

        using var resp = await CreateRawHttp().SendAsync(req);
        var body = await resp.Content.ReadAsStringAsync();
        if (!resp.IsSuccessStatusCode)
        {
            throw new InvalidOperationException(
                $"ObtenerTemplateCircuitoFirmaAsync: {(int)resp.StatusCode} {resp.StatusCode}\n{body}");
        }

        using var doc = JsonDocument.Parse(body);
        if (!doc.RootElement.TryGetProperty("content", out var content) || content.ValueKind != JsonValueKind.Array)
        {
            throw new InvalidOperationException("No se ha podido leer el listado de plantillas de circuito.");
        }

        var templates = new List<CircuitTemplateCandidate>();
        foreach (var item in content.EnumerateArray())
        {
            var name = item.TryGetProperty("name", out var pName) ? pName.GetString() : null;
            var href = GetSelfHref(item);
            if (string.IsNullOrWhiteSpace(href))
            {
                continue;
            }

            var payload = JsonNode.Parse(item.GetRawText()) as JsonObject ?? new JsonObject();
            templates.Add(new CircuitTemplateCandidate(
                Name: name,
                Href: href!,
                Payload: payload,
                SignersCount: CountArrayItems(payload, "signers"),
                BlockEdit: GetBoolean(payload, "block_edit")));
        }

        if (templates.Count == 0)
        {
            throw new InvalidOperationException("No hay plantillas de circuito disponibles para el documento.");
        }

        _logger.LogInformation(
            "Plantillas de circuito para {DocumentUrl}: {Templates}",
            documentUrl,
            string.Join(
                " | ",
                templates.Select(template =>
                    $"{template.Name ?? "(sin nombre)"} [firmantes={template.SignersCount}, candado={template.BlockEdit}]")));

        if (!string.IsNullOrWhiteSpace(PreferredCircuitTemplateName))
        {
            var configuredExact = templates.FirstOrDefault(template =>
                string.Equals(template.Name, PreferredCircuitTemplateName, StringComparison.OrdinalIgnoreCase));
            if (configuredExact is not null)
            {
                return configuredExact;
            }

            var configuredContains = templates.FirstOrDefault(template =>
                !string.IsNullOrWhiteSpace(template.Name) &&
                template.Name.Contains(PreferredCircuitTemplateName, StringComparison.OrdinalIgnoreCase));
            if (configuredContains is not null)
            {
                return configuredContains;
            }
        }

        string[] preferredNames =
        [
            "CT-Actualizacion de denuncia",
            "CT-Actualizaci\u00f3n de denuncia",
            "Firma automatizada",
            "Firma Sello de \u00D3rgano",
            "Firma Sello de Organo"
        ];

        var preferredTemplate = templates.FirstOrDefault(template =>
            preferredNames.Any(preferred =>
                string.Equals(template.Name, preferred, StringComparison.OrdinalIgnoreCase)));
        if (preferredTemplate is not null)
        {
            return preferredTemplate;
        }

        var templatesWithSigners = templates
            .Where(template => template.SignersCount > 0)
            .OrderByDescending(template => template.BlockEdit)
            .ThenByDescending(template => template.SignersCount)
            .ToList();
        if (templatesWithSigners.Count > 0)
        {
            return templatesWithSigners[0];
        }

        return templates[0];
    }

    private static JsonObject BuildCircuitPayloadFromTemplate(
        CircuitTemplateCandidate template,
        string assignedGroupHref,
        int? complaintId)
    {
        _ = assignedGroupHref;
        _ = complaintId;

        var payload = (JsonObject)template.Payload.DeepClone();
        EnsureTemplateSelfLink(payload, template.Href);
        payload.Remove("assigneds_can_use");
        return payload;
    }

    private static void EnsureTemplateSelfLink(JsonObject payload, string templateSelfHref)
    {
        if (payload["links"] is not JsonArray links)
        {
            links = new JsonArray();
            payload["links"] = links;
        }

        foreach (var node in links)
        {
            if (node is not JsonObject link)
            {
                continue;
            }

            var rel = link["rel"]?.GetValue<string>();
            var href = link["href"]?.GetValue<string>();
            if (string.Equals(rel, "self", StringComparison.OrdinalIgnoreCase) &&
                !string.IsNullOrWhiteSpace(href))
            {
                return;
            }
        }

        links.Add(JsonSerializer.SerializeToNode(new { rel = "self", href = templateSelfHref }));
    }

    private static int CountArrayItems(JsonObject payload, string propertyName)
    {
        return payload[propertyName] is JsonArray array ? array.Count : 0;
    }

    private static bool GetBoolean(JsonObject payload, string propertyName)
    {
        return payload[propertyName]?.GetValue<bool?>() ?? false;
    }

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

        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))
            {
                return href;
            }
        }

        return null;
    }

    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 sealed record CircuitTemplateCandidate(
        string? Name,
        string Href,
        JsonObject Payload,
        int SignersCount,
        bool BlockEdit);
}
