using JobTrackerApi.Data;
using JobTrackerApi.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

namespace JobTrackerApi.Services;

public sealed class FollowUpReminderHostedService(
    BackgroundTenantRunner tenants,
    IConfiguration configuration,
    ILogger<FollowUpReminderHostedService> logger,
    IStartupReadiness startupReadiness,
    ExternalOrigin externalOrigin,
    TimeProvider timeProvider) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await startupReadiness.WaitUntilReadyAsync(stoppingToken);
        if (!IsEnabled())
        {
            logger.LogInformation("Follow-up reminder worker disabled; both Workers:FollowUpRemindersEnabled and Email:FollowUpReminders:Enabled must be true.");
            return;
        }

        await Task.Delay(TimeSpan.FromSeconds(10), timeProvider, stoppingToken);
        while (!stoppingToken.IsCancellationRequested)
        {
            await RunOnceAsync(stoppingToken);
            await Task.Delay(TimeSpan.FromHours(6), timeProvider, stoppingToken);
        }
    }

    public Task<BackgroundWorkerRunResult> RunOnceAsync(CancellationToken cancellationToken)
    {
        if (!IsEnabled()) return Task.FromResult(BackgroundWorkerRunResult.Disabled);
        return tenants.RunForJobOwnersAsync("follow-up-reminders", ProcessOwnerAsync, cancellationToken);
    }

    private bool IsEnabled() =>
        configuration.GetValue("Workers:FollowUpRemindersEnabled", false) &&
        configuration.GetValue("Email:FollowUpReminders:Enabled", false);

    private async Task ProcessOwnerAsync(IServiceProvider services, CancellationToken cancellationToken)
    {
        var db = services.GetRequiredService<JobTrackerContext>();
        var users = services.GetRequiredService<UserManager<ApplicationUser>>();
        var email = services.GetRequiredService<IAppEmailSender>();
        var settings = await RulesEngine.GetSettings(db, cancellationToken);
        var now = timeProvider.GetLocalNow().DateTime;
        var lookAheadDays = Math.Clamp(configuration.GetValue("Email:FollowUpReminders:UpcomingDays", 2), 1, 14);
        var upcomingTo = now.AddDays(lookAheadDays);
        var lastMessages = await db.Correspondences.AsNoTracking()
            .GroupBy(message => message.JobApplicationId)
            .Select(group => new { JobApplicationId = group.Key, Last = group.Max(x => x.Date) })
            .ToDictionaryAsync(x => x.JobApplicationId, x => (DateTime?)x.Last, cancellationToken);
        var jobs = await db.JobApplications.Include(job => job.Company)
            .Where(job => !job.IsDeleted && job.OwnerUserId != null)
            .Where(job =>
                (job.FollowUpAt != null && job.FollowUpAt <= upcomingTo) ||
                job.Status == "Applied" ||
                job.Status == "Waiting" ||
                job.Status == "Offer" ||
                (job.Status == "Rejected" && job.FeedbackRequestedAt != null))
            .ToListAsync(cancellationToken);

        foreach (var job in jobs)
        {
            if (job.OwnerUserId is null || job.LastReminderEmailSentAt?.Date == now.Date) continue;
            lastMessages.TryGetValue(job.Id, out var lastMessage);
            var decision = RulesEngine.Evaluate(settings, job, now, lastMessage);
            var upcoming = job.FollowUpAt is not null && job.FollowUpAt.Value <= upcomingTo;
            if (!decision.NeedsFollowUp && !upcoming) continue;

            var owner = await users.FindByIdAsync(job.OwnerUserId);
            if (owner is null || !owner.EmailFollowUpRemindersEnabled || !owner.EmailConfirmed || string.IsNullOrWhiteSpace(owner.Email)) continue;

            var followMode = SuggestFollowUpMode(job.Status);
            var detailsUrl = externalOrigin.BuildPath($"/jobs?open={job.Id}&tab=4&followMode={Uri.EscapeDataString(followMode)}");
            var companyName = job.Company?.Name ?? "Unknown company";
            var appliedOn = job.DateApplied?.ToString("MMMM d, yyyy") ?? "an unrecorded date";
            var subject = $"Follow up reminder: {job.JobTitle} at {companyName}";
            var body = string.Join("\n\n", new[]
            {
                $"Hi {(owner.UserName ?? owner.Email ?? "there")},",
                $"This is your Jobbjakt reminder to follow up on the {job.JobTitle} role at {companyName}.",
                $"Applied on: {appliedOn}\nCurrent status: {job.Status}\nWhy now: {BuildReminderReason(job, decision.Reason, upcoming)}",
                $"Open the follow-up generator for this job:\n{detailsUrl}",
                "Tip: review the generated follow-up draft, candidate-fit notes, and recruiter message before sending.",
                "— Jobbjakt"
            });

            await email.SendAsync(owner.Email!, subject, body, cancellationToken);
            job.LastReminderEmailSentAt = now;
        }

        await db.SaveChangesAsync(cancellationToken);
    }

    private static string BuildReminderReason(JobApplication job, string? engineReason, bool upcoming)
    {
        if (upcoming && job.FollowUpAt is not null)
            return $"a follow-up date is scheduled for {job.FollowUpAt.Value:MMMM d, yyyy}";
        if (!string.IsNullOrWhiteSpace(engineReason)) return engineReason.Trim();
        return job.Status switch
        {
            "Applied" => "you applied and have not logged a response yet",
            "Waiting" => "you are waiting on next steps",
            "Offer" => "the process appears to be stalled after progress",
            _ => "the application may need attention"
        };
    }

    private static string SuggestFollowUpMode(string? status) => (status ?? string.Empty).Trim() switch
    {
        "Waiting" => "waiting-update",
        "Interview" or "Interviewing" => "post-interview",
        "Offer" => "offer-checkin",
        "Rejected" => "feedback-request",
        _ => "post-apply",
    };
}
