using System.Net;
using System.Text.RegularExpressions;
using JobsMedical.Web.Models;

namespace JobsMedical.Web.Services.Scraping;

/// <summary>
/// Reads public Telegram channels via the web preview (https://t.me/s/&lt;channel&gt;) — no bot
/// token needed for public channels. Enabled + channel list come from the admin settings (DB).
/// </summary>
public class TelegramListingSource : IListingSource
{
    private readonly ScrapeHttpClients _clients;
    private readonly ILogger<TelegramListingSource> _log;

    public TelegramListingSource(ScrapeHttpClients clients, ILogger<TelegramListingSource> log)
    {
        _clients = clients;
        _log = log;
    }

    public string Name => "تلگرام";

    public async Task<IReadOnlyList<ScrapedItem>> FetchAsync(AppSetting s, CancellationToken ct = default)
    {
        var channels = AppSetting.SplitList(s.TelegramChannels);
        if (!s.TelegramEnabled || channels.Count == 0) return Array.Empty<ScrapedItem>();

        var client = _clients.For(s, s.TelegramUseProxy);
        var items = new List<ScrapedItem>();
        foreach (var ch in channels.Select(c => c.TrimStart('@')).Where(c => c.Length > 0))
        {
            try
            {
                var html = await client.GetStringAsync($"https://t.me/s/{ch}", ct);
                foreach (var (text, postedAt) in ExtractMessages(html).Take(20))
                    items.Add(new ScrapedItem($"تلگرام/{ch}", text, $"https://t.me/{ch}", PostedAt: postedAt));
            }
            catch (Exception ex) { _log.LogWarning(ex, "Telegram fetch failed for {Channel}", ch); }
        }
        return items;
    }

    private static IEnumerable<(string text, DateTime? postedAt)> ExtractMessages(string html)
    {
        foreach (Match m in Regex.Matches(html,
            "<div class=\"tgme_widget_message_text[^\"]*\"[^>]*>(.*?)</div>", RegexOptions.Singleline))
        {
            var text = HtmlUtil.ToPlainText(m.Groups[1].Value);
            if (text.Length < 15) continue;
            // The message's date link (<time datetime="…">) follows its text in the same bubble —
            // grab the nearest one after this match.
            DateTime? postedAt = null;
            var tail = html.Substring(m.Index + m.Length, Math.Min(2000, html.Length - (m.Index + m.Length)));
            var dm = Regex.Match(tail, "datetime=\"([^\"]+)\"");
            if (dm.Success && DateTimeOffset.TryParse(dm.Groups[1].Value, out var dto)) postedAt = dto.UtcDateTime;
            yield return (text, postedAt);
        }
    }
}

internal static class HtmlUtil
{
    public static string ToPlainText(string html)
    {
        var s = Regex.Replace(html, "<br\\s*/?>", "\n", RegexOptions.IgnoreCase);
        s = Regex.Replace(s, "<[^>]+>", "");
        s = WebUtility.HtmlDecode(s);
        s = Regex.Replace(s, "[ \\t]+", " ");
        return s.Trim();
    }

    /// <summary>Best-effort age (in days) of a post from a Persian "time ago" phrase in its text
    /// («دیروز»، «۳ روز پیش»، «هفته پیش»، «۲ هفته پیش»، «ماه پیش»…). Divar embeds this in the row
    /// text, so we can age-filter it without a real timestamp. Now/minutes/hours → 0; null when no
    /// such phrase is present (caller then treats age as unknown).</summary>
    public static int? AgeDaysFromPersianText(string? text)
    {
        if (string.IsNullOrEmpty(text)) return null;
        var t = ToLatinDigits(text);
        if (Regex.IsMatch(t, "لحظات|هم[‌ ]?اکنون|چند لحظه|دقیقه پیش|دقایقی پیش|ساعت پیش|ساعتی پیش")) return 0;
        if (t.Contains("پریروز")) return 2;
        if (t.Contains("دیروز")) return 1;
        var m = Regex.Match(t, @"(\d+)\s*(روز|هفته|ماه|سال)\s*پیش");
        if (m.Success)
        {
            var n = int.Parse(m.Groups[1].Value);
            return m.Groups[2].Value switch
            { "روز" => n, "هفته" => n * 7, "ماه" => n * 30, "سال" => n * 365, _ => (int?)null };
        }
        if (Regex.IsMatch(t, @"هفته\s*پیش")) return 7;            // bare «هفته پیش» = ۱ هفته
        if (Regex.IsMatch(t, @"ماه\s*پیش")) return 30;
        if (Regex.IsMatch(t, @"سال\s*پیش") || t.Contains("پارسال")) return 365;
        return null;
    }

    /// <summary>Convert Persian/Arabic-Indic digits to Latin.</summary>
    public static string ToLatinDigits(string s)
    {
        var a = s.ToCharArray();
        for (var i = 0; i < a.Length; i++)
        {
            if (a[i] >= '۰' && a[i] <= '۹') a[i] = (char)('0' + (a[i] - '۰'));
            else if (a[i] >= '٠' && a[i] <= '٩') a[i] = (char)('0' + (a[i] - '٠'));
        }
        return new string(a);
    }

    /// <summary>
    /// Pull Iranian phone numbers out of a page's HTML — including ones a site reveals on click
    /// (often still present as a tel: link, a data-*phone* attribute, or JSON-LD "telephone").
    /// Returns normalized numbers (mobiles as 09xxxxxxxxx, landlines with leading 0), mobiles first.
    /// </summary>
    public static List<string> HarvestPhones(string html)
    {
        if (string.IsNullOrEmpty(html)) return new();
        var latin = ToLatinDigits(html);
        var found = new List<string>();

        void Add(string raw)
        {
            var d = Regex.Replace(raw, @"\D", "");
            if (d.StartsWith("0098")) d = "0" + d[4..];
            else if (d.StartsWith("98") && d.Length >= 12) d = "0" + d[2..];
            if (Regex.IsMatch(d, @"^9\d{9}$")) d = "0" + d;          // 9xxxxxxxxx → 09xxxxxxxxx
            bool ok = Regex.IsMatch(d, @"^09\d{9}$")                  // mobile
                   || Regex.IsMatch(d, @"^0\d{10}$");                 // landline w/ area code
            if (ok && !found.Contains(d)) found.Add(d);
        }

        // Highest-signal sources first.
        foreach (Match m in Regex.Matches(latin, @"tel:\+?([\d\s\-]{7,})")) Add(m.Groups[1].Value);
        foreach (Match m in Regex.Matches(latin, "\"telephone\"\\s*:\\s*\"([^\"]+)\"")) Add(m.Groups[1].Value);
        foreach (Match m in Regex.Matches(latin, "data-[\\w-]*phone[\\w-]*=[\"']([^\"']+)[\"']", RegexOptions.IgnoreCase)) Add(m.Groups[1].Value);
        // Then bare numbers anywhere in the text — mobiles, then landlines. The (?<!\d)/(?!\d)
        // guards stop us from grabbing a slice of a longer numeric id / timestamp.
        foreach (Match m in Regex.Matches(latin, @"(?<!\d)(?:\+?98|0)?9\d{9}(?!\d)")) Add(m.Value);
        foreach (Match m in Regex.Matches(latin, @"(?<!\d)0\d{2,3}[\s-]?\d{7,8}(?!\d)")) Add(m.Value);

        return found.Take(3).ToList();
    }
}
