using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using MongoDB.Driver;
using Nalu.Web.Data;
using Nalu.Web.Data.Models;

namespace Nalu.Web.Enrichers;

public class ViaCepEnricher : IEnricher
{
    private readonly HttpClient _http;
    private readonly MongoDbContext _db;
    private readonly ILogger<ViaCepEnricher> _logger;

    public string Name => "viacep";

    public ViaCepEnricher(HttpClient http, MongoDbContext db, ILogger<ViaCepEnricher> logger)
    {
        _http = http;
        _db = db;
        _logger = logger;
    }

    public async Task<EnrichmentResult> EnrichAsync(string value, CancellationToken ct = default)
    {
        var cep = Regex.Replace(value, @"\D", "");
        if (cep.Length != 8) return EnrichmentResult.NotFound();

        // ── 1. MongoDB cache ─────────────────────────────────────────────────
        if (_db.IsConnected)
        {
            var cached = await _db.CepCache
                .Find(c => c.Cep == cep)
                .FirstOrDefaultAsync(ct);

            if (cached != null)
                return BuildResult(cached.Cep, cached.Logradouro, cached.Bairro,
                                   cached.Cidade, cached.Estado);
        }

        // ── 2. ViaCEP ────────────────────────────────────────────────────────
        try
        {
            var json = await _http.GetFromJsonAsync<JsonObject>(
                $"https://viacep.com.br/ws/{cep}/json/", ct);

            var erroNode = json?["erro"];
            bool hasError = erroNode is not null &&
                (erroNode.GetValue<string?>() == "true" ||
                 erroNode.GetValueKind() == JsonValueKind.True);

            if (!hasError && json != null)
            {
                var result = BuildResult(cep,
                    json["logradouro"]?.GetValue<string?>(),
                    json["bairro"]?.GetValue<string?>(),
                    json["localidade"]?.GetValue<string?>(),
                    json["uf"]?.GetValue<string?>());

                await SaveToCacheAsync(cep, result, "viacep", ct);
                return result;
            }
        }
        catch (Exception ex) when (ex is not OperationCanceledException)
        {
            _logger.LogWarning(ex, "Primary lookup failed for CEP {Cep}, trying fallback", cep);
        }

        // ── 3. BrasilAPI fallback ────────────────────────────────────────────
        try
        {
            var json = await _http.GetFromJsonAsync<JsonObject>(
                $"https://brasilapi.com.br/api/cep/v2/{cep}", ct);

            if (json != null)
            {
                var result = BuildResult(cep,
                    json["street"]?.GetValue<string?>(),
                    json["neighborhood"]?.GetValue<string?>(),
                    json["city"]?.GetValue<string?>(),
                    json["state"]?.GetValue<string?>());

                await SaveToCacheAsync(cep, result, "brasilapi", ct);
                return result;
            }
        }
        catch (Exception ex) when (ex is not OperationCanceledException)
        {
            _logger.LogWarning(ex, "Fallback lookup also failed for CEP {Cep}", cep);
        }

        return EnrichmentResult.NotFound();
    }

    private async Task SaveToCacheAsync(string cep, EnrichmentResult result, string source, CancellationToken ct)
    {
        if (!_db.IsConnected || result.Value == null) return;

        try
        {
            // Parse back from the JSON we just built to get individual fields
            var doc = JsonSerializer.Deserialize<JsonObject>(result.Value ?? "{}");
            if (doc == null) return;

            var entry = new CepCache
            {
                Cep             = cep,
                Logradouro      = doc["logradouro"]?.GetValue<string?>() ?? "",
                Bairro          = doc["bairro"]?.GetValue<string?>() ?? "",
                Cidade          = doc["cidade"]?.GetValue<string?>() ?? "",
                Estado          = doc["estado"]?.GetValue<string?>() ?? "",
                FormattedAddress = doc["formatted_address"]?.GetValue<string?>() ?? "",
                Source          = source,
                CachedAt        = DateTime.UtcNow,
            };

            await _db.CepCache.ReplaceOneAsync(
                c => c.Cep == cep,
                entry,
                new ReplaceOptions { IsUpsert = true },
                ct);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to cache CEP {Cep} in MongoDB", cep);
        }
    }

    private static EnrichmentResult BuildResult(string cep, string? logradouro, string? bairro, string? cidade, string? estado)
    {
        logradouro ??= "";
        bairro     ??= "";
        cidade     ??= "";
        estado     ??= "";

        var parts = new List<string>();
        if (!string.IsNullOrWhiteSpace(logradouro)) parts.Add(logradouro);
        if (!string.IsNullOrWhiteSpace(bairro))     parts.Add(bairro);
        var cityState = string.IsNullOrWhiteSpace(estado) ? cidade : $"{cidade}/{estado}";
        if (!string.IsNullOrWhiteSpace(cityState))  parts.Add(cityState);
        var formattedAddress = string.Join(", ", parts);

        var address = new
        {
            cep              = $"{cep[..5]}-{cep[5..]}",
            logradouro,
            bairro,
            cidade,
            estado,
            formatted_address = formattedAddress,
        };

        return EnrichmentResult.Ok(JsonSerializer.Serialize(address));
    }
}
