using System.Globalization;
using System.Text.Json;
using System.Text.RegularExpressions;

namespace Nalu.Web.PostProcessors;

/// Parses a date string in multiple formats and returns JSON: {"date":"YYYY-MM-DD","formatted":"DD/MM/YYYY"}.
/// Validates: not in future, not > 130 years ago, real calendar date.
public class ParseDate : IPostProcessor
{
    public string Name => "parse_date";

    private static readonly string[] Formats =
    [
        "yyyy-MM-dd",               // ISO
        "dd/MM/yyyy", "dd-MM-yyyy", "dd.MM.yyyy",
        "MM/dd/yyyy", "MM-dd-yyyy", // en-US (tried after DD/MM)
        "dd/MM/yy",   "MM/dd/yy",
        "d/M/yyyy",   "d-M-yyyy",
        "d/M/yy"
    ];

    // Month name tables
    private static readonly Dictionary<string, int> MonthsPt = new(StringComparer.OrdinalIgnoreCase)
    {
        {"janeiro",1},{"fevereiro",2},{"março",3},{"marco",3},{"abril",4},
        {"maio",5},{"junho",6},{"julho",7},{"agosto",8},{"setembro",9},
        {"outubro",10},{"novembro",11},{"dezembro",12}
    };
    private static readonly Dictionary<string, int> MonthsEn = new(StringComparer.OrdinalIgnoreCase)
    {
        {"january",1},{"february",2},{"march",3},{"april",4},{"may",5},
        {"june",6},{"july",7},{"august",8},{"september",9},{"october",10},
        {"november",11},{"december",12},
        {"jan",1},{"feb",2},{"mar",3},{"apr",4},{"jun",6},{"jul",7},
        {"aug",8},{"sep",9},{"oct",10},{"nov",11},{"dec",12}
    };
    private static readonly Dictionary<string, int> MonthsEs = new(StringComparer.OrdinalIgnoreCase)
    {
        {"enero",1},{"febrero",2},{"marzo",3},{"abril",4},{"mayo",5},
        {"junio",6},{"julio",7},{"agosto",8},{"septiembre",9},
        {"octubre",10},{"noviembre",11},{"diciembre",12}
    };

    public ProcessorResult Process(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
            return ProcessorResult.Invalid("Data não informada");

        var date = TryParseAll(value.Trim());
        if (date is null)
            return ProcessorResult.Invalid($"Formato de data não reconhecido: {value}");

        if (date.Value > DateTime.Today)
            return ProcessorResult.Invalid("Data de nascimento não pode ser no futuro");

        var age = ComputeAge(date.Value);
        if (age > 130)
            return ProcessorResult.Invalid("Data de nascimento improvável (mais de 130 anos atrás)");

        var json = JsonSerializer.Serialize(new
        {
            date = date.Value.ToString("yyyy-MM-dd"),
            formatted = date.Value.ToString("dd/MM/yyyy")
        });

        return ProcessorResult.Ok(json);
    }

    private static DateTime? TryParseAll(string input)
    {
        // 1. Try standard formats
        if (DateTime.TryParseExact(input, Formats, CultureInfo.InvariantCulture,
            DateTimeStyles.None, out var dt))
            return dt;

        // 2. Try written month — pt: "15 de março de 1990"
        var mPt = Regex.Match(input,
            @"(\d{1,2})\s+de\s+(\w+)\s+de\s+(\d{2,4})", RegexOptions.IgnoreCase);
        if (mPt.Success && MonthsPt.TryGetValue(mPt.Groups[2].Value, out var moPt))
            return BuildDate(int.Parse(mPt.Groups[1].Value), moPt, ExpandYear(int.Parse(mPt.Groups[3].Value)));

        // 3. en: "March 15, 1990" / "March 15 1990"
        var mEn = Regex.Match(input,
            @"(\w+)\s+(\d{1,2})[,\s]+(\d{2,4})", RegexOptions.IgnoreCase);
        if (mEn.Success && MonthsEn.TryGetValue(mEn.Groups[1].Value, out var moEn))
            return BuildDate(int.Parse(mEn.Groups[2].Value), moEn, ExpandYear(int.Parse(mEn.Groups[3].Value)));

        // 4. es: "15 de marzo de 1990"
        var mEs = Regex.Match(input,
            @"(\d{1,2})\s+de\s+(\w+)\s+de\s+(\d{2,4})", RegexOptions.IgnoreCase);
        if (mEs.Success && MonthsEs.TryGetValue(mEs.Groups[2].Value, out var moEs))
            return BuildDate(int.Parse(mEs.Groups[1].Value), moEs, ExpandYear(int.Parse(mEs.Groups[3].Value)));

        return null;
    }

    private static DateTime? BuildDate(int day, int month, int year)
    {
        if (month < 1 || month > 12 || day < 1) return null;
        if (day > DateTime.DaysInMonth(year, month)) return null;
        return new DateTime(year, month, day);
    }

    private static int ExpandYear(int y) =>
        y < 100 ? (y > 30 ? 1900 + y : 2000 + y) : y;

    public static int ComputeAge(DateTime birth)
    {
        var today = DateTime.Today;
        var age = today.Year - birth.Year;
        if (birth.Date > today.AddYears(-age)) age--;
        return age;
    }
}
