using JobTrackerApi.Controllers;
using JobTrackerApi.Models;

namespace JobTrackerApi.Services;

public sealed record GmailMatchReason(string Label, string Value, int Points);
public sealed record GmailScoredMessageResult(
    GmailMessageSummary Message,
    bool AlreadyImported,
    int Score,
    string Confidence,
    IReadOnlyList<string> MatchedQueries,
    IReadOnlyList<GmailMatchReason> Reasons);

public interface IGmailJobMatchingService
{
    IReadOnlyList<string> BuildJobQueries(JobApplication job, string? queryOverride);
    GmailScoredMessageResult ScoreMessage(JobApplication job, GmailQueryMatchedMessage candidate, bool alreadyImported, bool threadAlreadyImported);
}

public sealed class GmailJobMatchingService : IGmailJobMatchingService
{
    public IReadOnlyList<string> BuildJobQueries(JobApplication job, string? queryOverride)
    {
        var queries = new List<string>();
        void Add(string? query)
        {
            if (!string.IsNullOrWhiteSpace(query))
            {
                queries.Add(query.Trim());
            }
        }

        Add(queryOverride);

        if (!string.IsNullOrWhiteSpace(job.Company?.RecruiterEmail))
            Add($"(from:{job.Company.RecruiterEmail.Trim()} OR to:{job.Company.RecruiterEmail.Trim()}) newer_than:365d");
        if (!string.IsNullOrWhiteSpace(job.Company?.RecruiterName))
            Add($"\"{job.Company.RecruiterName.Trim()}\" newer_than:365d");
        if (!string.IsNullOrWhiteSpace(job.Company?.Name) && !string.IsNullOrWhiteSpace(job.JobTitle))
            Add($"\"{job.Company.Name.Trim()}\" \"{job.JobTitle.Trim()}\" newer_than:365d");
        if (!string.IsNullOrWhiteSpace(job.Company?.Name))
            Add($"\"{job.Company.Name.Trim()}\" (application OR interview OR recruiter OR role OR position) newer_than:365d");
        if (!string.IsNullOrWhiteSpace(job.JobTitle))
            Add($"subject:\"{job.JobTitle.Trim()}\" newer_than:365d");

        foreach (var subject in job.Messages
                     .Select(message => message.Subject)
                     .Where(subject => !string.IsNullOrWhiteSpace(subject))
                     .Distinct(StringComparer.OrdinalIgnoreCase)
                     .Take(2))
        {
            Add($"subject:\"{subject!.Trim()}\" newer_than:365d");
        }

        if (queries.Count == 0)
            Add("newer_than:365d (application OR interview OR recruiter OR role OR position)");

        return queries.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
    }

    public GmailScoredMessageResult ScoreMessage(JobApplication job, GmailQueryMatchedMessage candidate, bool alreadyImported, bool threadAlreadyImported)
    {
        var reasons = new List<GmailMatchReason>();
        var score = 0;
        var message = candidate.Message;
        var subject = message.Subject ?? string.Empty;
        var from = message.From ?? string.Empty;
        var to = message.To ?? string.Empty;
        var snippet = message.Snippet ?? string.Empty;
        var haystack = $"{subject} {from} {to} {snippet}";

        if (candidate.MatchedQueries.Count > 0)
        {
            var queryHitPoints = Math.Min(12, candidate.MatchedQueries.Count * 4);
            score += queryHitPoints;
            reasons.Add(new GmailMatchReason("queryHits", candidate.MatchedQueries.Count.ToString(), queryHitPoints));
        }

        if (!string.IsNullOrWhiteSpace(job.Company?.Name) && ContainsValue(haystack, job.Company.Name))
        {
            score += 18;
            reasons.Add(new GmailMatchReason("company", job.Company.Name.Trim(), 18));
        }

        if (!string.IsNullOrWhiteSpace(job.Company?.RecruiterEmail) && (ContainsValue(from, job.Company.RecruiterEmail) || ContainsValue(to, job.Company.RecruiterEmail)))
        {
            score += 20;
            reasons.Add(new GmailMatchReason("recruiterEmail", job.Company.RecruiterEmail.Trim(), 20));
        }

        if (!string.IsNullOrWhiteSpace(job.Company?.RecruiterName) && ContainsValue(haystack, job.Company.RecruiterName))
        {
            score += 12;
            reasons.Add(new GmailMatchReason("recruiter", job.Company.RecruiterName.Trim(), 12));
        }

        foreach (var token in SplitTerms(job.JobTitle).Take(4))
        {
            if (!ContainsValue(haystack, token)) continue;
            score += 5;
            reasons.Add(new GmailMatchReason("jobTitle", token, 5));
        }

        foreach (var subjectLine in job.Messages
                     .Select(existing => existing.Subject)
                     .Where(existing => !string.IsNullOrWhiteSpace(existing))
                     .Distinct(StringComparer.OrdinalIgnoreCase)
                     .Take(2))
        {
            if (!ContainsValue(subject, subjectLine!)) continue;
            score += 8;
            reasons.Add(new GmailMatchReason("existingSubject", subjectLine!.Trim(), 8));
        }

        if (message.Date is { } messageDate)
        {
            var ageDays = Math.Abs((DateTimeOffset.UtcNow - messageDate).TotalDays);
            if (ageDays <= 45)
            {
                score += 4;
                reasons.Add(new GmailMatchReason("recency", "45d", 4));
            }
            else if (ageDays <= 180)
            {
                score += 2;
                reasons.Add(new GmailMatchReason("recency", "180d", 2));
            }
        }

        if (threadAlreadyImported && !alreadyImported)
            reasons.Add(new GmailMatchReason("status", "thread-already-imported", 0));
        if (alreadyImported)
            reasons.Add(new GmailMatchReason("status", "already-imported", 0));

        reasons = reasons
            .GroupBy(reason => new { reason.Label, reason.Value, reason.Points })
            .Select(group => group.First())
            .OrderByDescending(reason => reason.Points)
            .ThenBy(reason => reason.Label, StringComparer.Ordinal)
            .ThenBy(reason => reason.Value, StringComparer.Ordinal)
            .ToList();

        return new GmailScoredMessageResult(message, alreadyImported, score, ToConfidence(score), candidate.MatchedQueries, reasons);
    }

    private static bool ContainsValue(string haystack, string? value)
        => !string.IsNullOrWhiteSpace(value) && haystack.Contains(value.Trim(), StringComparison.OrdinalIgnoreCase);

    private static IEnumerable<string> SplitTerms(string? value)
    {
        if (string.IsNullOrWhiteSpace(value)) yield break;
        foreach (var token in value.Split(new[] { ' ', '/', '-', ',', '.', '(', ')', ':' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
                     .Where(token => token.Length >= 3)
                     .Distinct(StringComparer.OrdinalIgnoreCase))
        {
            yield return token;
        }
    }

    private static string ToConfidence(int score) => score switch
    {
        >= 30 => "high",
        >= 16 => "medium",
        _ => "low"
    };
}
