using System.Security.Claims;
using JobTrackerApi.Controllers;
using JobTrackerApi.Data;
using JobTrackerApi.Models;
using JobTrackerApi.Services;
using JobTrackerApi.Tests.TestSupport;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;

namespace JobTrackerApi.Tests;

/// <summary>
/// Moving a job backwards into a pre-application stage clears DateApplied so analytics stay
/// accurate. These tests guard the other half of that bargain: the application activity must be
/// preserved as history rather than destroyed.
/// </summary>
public sealed class JobApplicationsAppliedDateHistoryTests
{
    private static readonly DateTime AppliedOn = new(2026, 3, 1, 9, 30, 0, DateTimeKind.Utc);

    [Fact]
    public async Task Moving_back_to_a_prospect_stage_clears_the_date_but_records_it_as_history()
    {
        await using var db = CreateDb();
        var job = await SeedAppliedJob(db);
        var controller = CreateController(db, "user-1");

        await controller.UpdateStatus(job.Id, new UpdateStatusRequest("Saved"), CancellationToken.None);

        var saved = await db.JobApplications.SingleAsync(j => j.Id == job.Id);
        Assert.Equal("Saved", saved.Status);
        Assert.Null(saved.DateApplied);

        var cleared = await db.JobEvents.SingleAsync(e => e.Type == JobPipeline.AppliedDateClearedEvent);
        // Round-tripped, so the original date is recoverable exactly — not just described in prose.
        Assert.Equal(AppliedOn, DateTime.Parse(cleared.OldValue!, null, System.Globalization.DateTimeStyles.RoundtripKind));

        // The status transition itself is still recorded separately.
        Assert.Contains(await db.JobEvents.ToListAsync(), e => e.Type == "StatusChanged" && e.NewValue == "Saved");
    }

    [Fact]
    public async Task Re_applying_after_a_backwards_move_stamps_a_fresh_date_and_leaves_history_intact()
    {
        await using var db = CreateDb();
        var job = await SeedAppliedJob(db);
        var controller = CreateController(db, "user-1");

        await controller.UpdateStatus(job.Id, new UpdateStatusRequest("Saved"), CancellationToken.None);
        await controller.UpdateStatus(job.Id, new UpdateStatusRequest("Applied"), CancellationToken.None);

        var saved = await db.JobApplications.SingleAsync(j => j.Id == job.Id);
        Assert.Equal("Applied", saved.Status);
        Assert.NotNull(saved.DateApplied);
        Assert.NotEqual(AppliedOn, saved.DateApplied);

        // The first application's date survives the round trip in history.
        var cleared = await db.JobEvents.SingleAsync(e => e.Type == JobPipeline.AppliedDateClearedEvent);
        Assert.Equal(AppliedOn, DateTime.Parse(cleared.OldValue!, null, System.Globalization.DateTimeStyles.RoundtripKind));
    }

    [Fact]
    public async Task Moving_between_post_application_stages_records_no_cleared_date_event()
    {
        await using var db = CreateDb();
        var job = await SeedAppliedJob(db);
        var controller = CreateController(db, "user-1");

        await controller.UpdateStatus(job.Id, new UpdateStatusRequest("Interview"), CancellationToken.None);

        var saved = await db.JobApplications.SingleAsync(j => j.Id == job.Id);
        Assert.Equal(AppliedOn, saved.DateApplied);
        Assert.DoesNotContain(await db.JobEvents.ToListAsync(), e => e.Type == JobPipeline.AppliedDateClearedEvent);
    }

    [Fact]
    public async Task Withdrawing_keeps_the_applied_date()
    {
        await using var db = CreateDb();
        var job = await SeedAppliedJob(db);
        var controller = CreateController(db, "user-1");

        await controller.UpdateStatus(job.Id, new UpdateStatusRequest("Withdrawn"), CancellationToken.None);

        // Withdrawn is a closed stage, not a pre-application one: you did apply, then pulled out.
        var saved = await db.JobApplications.SingleAsync(j => j.Id == job.Id);
        Assert.Equal("Withdrawn", saved.Status);
        Assert.Equal(AppliedOn, saved.DateApplied);
    }

    private static async Task<JobApplication> SeedAppliedJob(JobTrackerContext db)
    {
        var company = new Company { Name = "Acme", OwnerUserId = "user-1" };
        db.Companies.Add(company);
        await db.SaveChangesAsync();

        var job = new JobApplication
        {
            JobTitle = "Backend Developer",
            CompanyId = company.Id,
            OwnerUserId = "user-1",
            Status = "Applied",
            DateApplied = AppliedOn,
            SavedAt = AppliedOn.AddDays(-2),
        };
        db.JobApplications.Add(job);
        await db.SaveChangesAsync();
        return job;
    }

    private static JobApplicationsController CreateController(JobTrackerContext db, string userId)
    {
        var controller = new JobApplicationsController(db, Mock.Of<ISummarizerService>(), TestHostFactory.CreateUserManager().Object);
        controller.ControllerContext = new ControllerContext
        {
            HttpContext = new DefaultHttpContext
            {
                User = new ClaimsPrincipal(new ClaimsIdentity(new[]
                {
                    new Claim(ClaimTypes.NameIdentifier, userId)
                }, "test"))
            }
        };
        return controller;
    }

    private static JobTrackerContext CreateDb() => TestHostFactory.CreateInMemoryDb();
}
