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

namespace JobTrackerApi.Services;

// Phase 5.5 — Interview preparation and follow-up.
//
// The prep content is the USER's. AI suggestions arrive through the existing AiWorkspaceService
// "interview" module and only become prep items when the user accepts them, which is what approval
// means here. Nothing in this service writes to the CareerProfile, a CvVariant, or the application's
// own fields — except FollowUpAt, which is the one thing a follow-up genuinely is.
//
// Follow-ups reuse what already exists: JobApplication.FollowUpAt for the date (RulesEngine and the
// reminder hosted service already act on it) and ApplicationChecklistItem for the task. No second
// reminder system. docs/architecture/application-workspace.md.
public sealed record InterviewPrepItemDto(
    int Id,
    string Category,
    string Title,
    string? Content,
    string Source,
    bool IsPrepared,
    int SortOrder,
    DateTimeOffset UpdatedAtUtc);

public sealed record InterviewPrepGroupDto(string Category, string Label, IReadOnlyList<InterviewPrepItemDto> Items);

public sealed record InterviewPrepBoardDto(
    IReadOnlyList<InterviewPrepGroupDto> Groups,
    int Total,
    int Prepared,
    int Percent,
    bool IsInterviewStage,
    int AiSuggestionCount);

public sealed record InterviewPrepInput(string? Category, string? Title, string? Content, string? Source, bool? IsPrepared);

public sealed record FollowUpDto(DateTime? FollowUpAt, string? NextAction, bool ResponseReceived, int OpenFollowUpTasks);

public interface IInterviewPrepService
{
    Task<InterviewPrepBoardDto?> GetAsync(string ownerUserId, int jobApplicationId, CancellationToken ct);
    Task<InterviewPrepItemDto?> AddAsync(string ownerUserId, int jobApplicationId, InterviewPrepInput input, CancellationToken ct);
    Task<InterviewPrepItemDto?> UpdateAsync(string ownerUserId, int jobApplicationId, int itemId, InterviewPrepInput input, CancellationToken ct);
    Task<bool> DeleteAsync(string ownerUserId, int jobApplicationId, int itemId, CancellationToken ct);

    Task<FollowUpDto?> GetFollowUpAsync(string ownerUserId, int jobApplicationId, CancellationToken ct);
    Task<FollowUpDto?> SetFollowUpAsync(string ownerUserId, int jobApplicationId, DateTime? followUpAt, string? nextAction, CancellationToken ct);
}

public sealed class InterviewPrepService : IInterviewPrepService
{
    private static readonly Dictionary<string, string> Labels = new(StringComparer.Ordinal)
    {
        [InterviewPrepCategories.CompanyResearch] = "Company research",
        [InterviewPrepCategories.Technical] = "Technical preparation",
        [InterviewPrepCategories.Behavioural] = "Behavioural questions",
        [InterviewPrepCategories.Star] = "STAR examples",
        [InterviewPrepCategories.Question] = "Questions to ask them",
        [InterviewPrepCategories.Note] = "Notes",
    };

    private readonly JobTrackerContext _db;

    public InterviewPrepService(JobTrackerContext db)
    {
        _db = db;
    }

    public async Task<InterviewPrepBoardDto?> GetAsync(string ownerUserId, int jobApplicationId, CancellationToken ct)
    {
        var job = await LoadJobAsync(ownerUserId, jobApplicationId, ct);
        if (job is null) return null;

        var items = await _db.InterviewPrepItems.AsNoTracking()
            .Where(i => i.OwnerUserId == ownerUserId && i.JobApplicationId == jobApplicationId)
            .ToListAsync(ct);

        var aiCount = await _db.AiInteractions.AsNoTracking()
            .CountAsync(a => a.OwnerUserId == ownerUserId && a.JobApplicationId == jobApplicationId && a.Module == "interview", ct);

        var groups = items
            .GroupBy(i => i.Category)
            .OrderBy(g => InterviewPrepCategories.Rank(g.Key))
            .Select(g => new InterviewPrepGroupDto(
                g.Key,
                Labels.TryGetValue(g.Key, out var label) ? label : g.Key,
                g.OrderBy(i => i.SortOrder).ThenBy(i => i.Id).Select(Project).ToList()))
            .ToList();

        var prepared = items.Count(i => i.IsPrepared);

        return new InterviewPrepBoardDto(
            groups,
            items.Count,
            prepared,
            items.Count == 0 ? 0 : (int)Math.Round(prepared * 100.0 / items.Count),
            IsInterviewStage(job.Status),
            aiCount);
    }

    public async Task<InterviewPrepItemDto?> AddAsync(string ownerUserId, int jobApplicationId, InterviewPrepInput input, CancellationToken ct)
    {
        var title = (input.Title ?? string.Empty).Trim();
        if (title.Length == 0) return null;

        var job = await LoadJobAsync(ownerUserId, jobApplicationId, ct);
        if (job is null) return null;

        var maxSort = await _db.InterviewPrepItems
            .Where(i => i.OwnerUserId == ownerUserId && i.JobApplicationId == jobApplicationId)
            .Select(i => (int?)i.SortOrder)
            .MaxAsync(ct) ?? 0;

        var item = new InterviewPrepItem
        {
            OwnerUserId = ownerUserId,
            JobApplicationId = jobApplicationId,
            Category = InterviewPrepCategories.IsValid(input.Category) ? input.Category! : InterviewPrepCategories.Note,
            Title = title,
            Content = Blank(input.Content),
            // An accepted AI suggestion is recorded as such, but is fully the user's to edit after.
            Source = InterviewPrepSources.IsValid(input.Source) ? input.Source! : InterviewPrepSources.User,
            IsPrepared = input.IsPrepared ?? false,
            SortOrder = maxSort + 1,
        };

        _db.InterviewPrepItems.Add(item);
        await _db.SaveChangesAsync(ct);
        return Project(item);
    }

    public async Task<InterviewPrepItemDto?> UpdateAsync(string ownerUserId, int jobApplicationId, int itemId, InterviewPrepInput input, CancellationToken ct)
    {
        var item = await _db.InterviewPrepItems
            .FirstOrDefaultAsync(i => i.Id == itemId && i.OwnerUserId == ownerUserId && i.JobApplicationId == jobApplicationId, ct);
        if (item is null) return null;

        if (!string.IsNullOrWhiteSpace(input.Title)) item.Title = input.Title!.Trim();
        if (input.Content is not null) item.Content = Blank(input.Content);
        if (InterviewPrepCategories.IsValid(input.Category)) item.Category = input.Category!;
        if (input.IsPrepared is not null) item.IsPrepared = input.IsPrepared.Value;
        item.UpdatedAtUtc = DateTimeOffset.UtcNow;

        await _db.SaveChangesAsync(ct);
        return Project(item);
    }

    public async Task<bool> DeleteAsync(string ownerUserId, int jobApplicationId, int itemId, CancellationToken ct)
    {
        var item = await _db.InterviewPrepItems
            .FirstOrDefaultAsync(i => i.Id == itemId && i.OwnerUserId == ownerUserId && i.JobApplicationId == jobApplicationId, ct);
        if (item is null) return false;

        _db.InterviewPrepItems.Remove(item);
        await _db.SaveChangesAsync(ct);
        return true;
    }

    // ---------- follow-up ----------

    public async Task<FollowUpDto?> GetFollowUpAsync(string ownerUserId, int jobApplicationId, CancellationToken ct)
    {
        var job = await LoadJobAsync(ownerUserId, jobApplicationId, ct);
        if (job is null) return null;
        return await BuildFollowUpAsync(ownerUserId, job, ct);
    }

    // Sets the date the existing reminder machinery already reads, and records a JobEvent so the
    // timeline shows it. The follow-up TASK itself stays a checklist item — this does not invent a
    // second to-do list.
    public async Task<FollowUpDto?> SetFollowUpAsync(string ownerUserId, int jobApplicationId, DateTime? followUpAt, string? nextAction, CancellationToken ct)
    {
        var job = await _db.JobApplications
            .FirstOrDefaultAsync(j => j.Id == jobApplicationId && j.OwnerUserId == ownerUserId, ct);
        if (job is null) return null;

        var previous = job.FollowUpAt;
        job.FollowUpAt = followUpAt;
        if (nextAction is not null) job.NextAction = Blank(nextAction);

        // Same event type the rest of the app already emits, so the timeline reads it unchanged.
        _db.JobEvents.Add(new JobEvent
        {
            JobApplicationId = jobApplicationId,
            Type = "FollowUpSet",
            OldValue = previous?.ToString("yyyy-MM-dd"),
            NewValue = followUpAt?.ToString("yyyy-MM-dd"),
            At = DateTime.Now,
        });

        await _db.SaveChangesAsync(ct);
        return await BuildFollowUpAsync(ownerUserId, job, ct);
    }

    private async Task<FollowUpDto> BuildFollowUpAsync(string ownerUserId, JobApplication job, CancellationToken ct)
    {
        // The follow-up tasks are checklist items — counted here, owned there.
        var openTasks = await _db.ApplicationChecklistItems.AsNoTracking()
            .CountAsync(i => i.OwnerUserId == ownerUserId
                && i.JobApplicationId == job.Id
                && i.Category == ChecklistCategories.FollowUp
                && i.Status == ChecklistStatuses.Pending, ct);

        return new FollowUpDto(job.FollowUpAt, job.NextAction, job.ResponseReceived, openTasks);
    }

    private Task<JobApplication?> LoadJobAsync(string ownerUserId, int jobApplicationId, CancellationToken ct) =>
        _db.JobApplications.AsNoTracking()
            .FirstOrDefaultAsync(j => j.Id == jobApplicationId && j.OwnerUserId == ownerUserId, ct);

    private static bool IsInterviewStage(string? status) =>
        (status ?? string.Empty).Contains("interview", StringComparison.OrdinalIgnoreCase);

    private static string? Blank(string? value) =>
        string.IsNullOrWhiteSpace(value) ? null : value.Trim();

    private static InterviewPrepItemDto Project(InterviewPrepItem i) =>
        new(i.Id, i.Category, i.Title, i.Content, i.Source, i.IsPrepared, i.SortOrder, i.UpdatedAtUtc);
}
