using System.Text.RegularExpressions;
using JobsMedical.Web.Data;
using JobsMedical.Web.Models;
using Microsoft.EntityFrameworkCore;

namespace JobsMedical.Web.Services;

/// <summary>
/// Anti-garbage gate for user-submitted listings: blocks duplicate positions, screens obvious
/// spam/garbage text, and prevents the same applicant applying to a listing twice.
/// </summary>
public class SubmissionGuard
{
    private readonly AppDbContext _db;
    public SubmissionGuard(AppDbContext db) => _db = db;

    private static readonly string[] SpamMarkers =
    {
        "سرمایه گذاری", "سرمایه‌گذاری", "وام", "ارز دیجیتال", "رمز ارز", "فروش فالوور",
        "بک لینک", "قرعه کشی", "کازینو", "شرط بندی", "بیت کوین", "http://", "https://", "www."
    };

    /// <summary>Spam/scam markers, mashed-keyboard, or stray links in free text (any field).</summary>
    public static bool ContainsSpam(string? text)
    {
        var t = (text ?? "").Trim();
        if (t.Length == 0) return false;
        if (SpamMarkers.Any(m => t.Contains(m))) return true;
        if (Regex.IsMatch(t, @"(.)\1{6,}")) return true;   // aaaaaaa / کککککککک
        return false;
    }

    /// <summary>For jobs: a real title is required, plus the spam screen on title+description.</summary>
    public static bool LooksLikeGarbage(string? title, string? description)
    {
        if (Normalize(title).Length < 3) return true;       // no real title
        return ContainsSpam(title) || ContainsSpam(description);
    }

    /// <summary>A near-identical OPEN shift already exists at this facility?</summary>
    public Task<bool> DuplicateShiftAsync(int facilityId, int roleId, DateOnly date,
        TimeOnly start, ShiftType type) =>
        _db.Shifts.AnyAsync(s => s.Status == ShiftStatus.Open && s.FacilityId == facilityId
            && s.RoleId == roleId && s.Date == date && s.StartTime == start && s.ShiftType == type);

    /// <summary>A near-identical OPEN job already exists at this facility (same role + title)?</summary>
    public async Task<bool> DuplicateJobAsync(int facilityId, int roleId, string title)
    {
        var t = Normalize(title);
        var candidates = await _db.JobOpenings
            .Where(j => j.Status == ShiftStatus.Open && j.FacilityId == facilityId && j.RoleId == roleId)
            .Select(j => j.Title).ToListAsync();
        return candidates.Any(c => Normalize(c) == t);
    }

    /// <summary>Max new listings (shifts + jobs) one account may post per rolling hour.</summary>
    public const int MaxListingsPerHour = 20;

    /// <summary>True if this owner has hit the hourly posting cap (flood protection).</summary>
    public async Task<bool> PostingRateExceededAsync(int ownerUserId)
    {
        var since = DateTime.UtcNow.AddHours(-1);
        var shifts = await _db.Shifts.CountAsync(s => s.Facility.OwnerUserId == ownerUserId && s.CreatedAt >= since);
        var jobs = await _db.JobOpenings.CountAsync(j => j.Facility.OwnerUserId == ownerUserId && j.CreatedAt >= since);
        return shifts + jobs >= MaxListingsPerHour;
    }

    /// <summary>Has this visitor already applied (Apply event) to this shift/job?</summary>
    public Task<bool> AlreadyAppliedAsync(string visitorId, int? shiftId, int? jobId) =>
        _db.InterestEvents.AnyAsync(e => e.VisitorId == visitorId
            && e.EventType == InterestEventType.Apply
            && ((shiftId != null && e.ShiftId == shiftId) || (jobId != null && e.JobOpeningId == jobId)));

    private static string Normalize(string? s) => Regex.Replace((s ?? "").Trim(), @"\s+", " ")
        .Replace('ي', 'ی').Replace('ك', 'ک').ToLowerInvariant();
}
