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.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;

namespace JobTrackerApi.Tests;

// Candidate fit and Strategy Snapshot share AiWorkspaceNote persistence. Strategy generation is
// durable now, so this file verifies its service-level result cache rather than invoking a model
// from the GET endpoint.
public sealed class AiWorkspaceNotePersistenceTests
{
    [Fact]
    public async Task GetCandidateFit_persists_and_reuses_the_generated_note()
    {
        await using var db = TestHostFactory.CreateInMemoryDb();
        var job = await SeedJobWithCvAsync(db);

        var summarizer = new Mock<ISummarizerService>();
        var callCount = 0;
        summarizer.Setup(x => x.SummarizeSectionAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
            .ReturnsAsync(() => { callCount++; return $"Summary call {callCount}"; });

        var controller = CreateController(db, summarizer.Object, "user-1");

        var first = await controller.GetCandidateFit(job.Id, null, false, CancellationToken.None);
        var callsAfterFirst = callCount;

        var second = await controller.GetCandidateFit(job.Id, null, false, CancellationToken.None);

        Assert.Equal(GetDto(first).MatchSummary, GetDto(second).MatchSummary);
        Assert.Equal(callsAfterFirst, callCount);

        var stored = Assert.Single(db.AiWorkspaceNotes.IgnoreQueryFilters().Where(x => x.JobApplicationId == job.Id && x.NoteType == "candidate-fit"));
        Assert.NotEmpty(stored.ResultJson);
    }

    [Fact]
    public async Task GetCandidateFit_regenerates_when_refresh_is_requested()
    {
        await using var db = TestHostFactory.CreateInMemoryDb();
        var job = await SeedJobWithCvAsync(db);

        var summarizer = new Mock<ISummarizerService>();
        var callCount = 0;
        summarizer.Setup(x => x.SummarizeSectionAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
            .ReturnsAsync(() => { callCount++; return $"Summary call {callCount}"; });

        var controller = CreateController(db, summarizer.Object, "user-1");

        await controller.GetCandidateFit(job.Id, null, false, CancellationToken.None);
        var callsAfterFirst = callCount;

        var refreshed = await controller.GetCandidateFit(job.Id, null, true, CancellationToken.None);

        Assert.True(callCount > callsAfterFirst);
        Assert.NotNull(GetDto(refreshed).MatchSummary);
    }

    [Fact]
    public async Task StrategySnapshot_generation_persists_a_stable_cached_result()
    {
        await using var db = TestHostFactory.CreateInMemoryDb();
        var job = await SeedJobWithCvAsync(db);

        var summarizer = new Mock<ISummarizerService>();
        summarizer.Setup(x => x.GenerateSectionWithMetadataAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(new AiGenerationResult("""{"strategicSummary":"Lead with backend delivery.","cvBulletIdeas":["Built APIs"],"proofPointsToLeadWith":["Led delivery"],"coverLetterAngles":["Relevant platform work"]}""", "ollama", "test-model"));

        var service = new StrategySnapshotService(db, summarizer.Object);
        var generated = await service.GenerateAsync(job.Id, [], CancellationToken.None);
        var cached = await service.GetCachedAsync(job.Id, string.Empty, CancellationToken.None);

        Assert.NotNull(cached);
        Assert.Equal(generated.Result.StrategicSummary, cached.StrategicSummary);
        Assert.Equal(generated.Result.CvBulletIdeas, cached.CvBulletIdeas);
        Assert.Equal(generated.Result.ProofPointsToLeadWith, cached.ProofPointsToLeadWith);
        Assert.Equal(generated.Result.CoverLetterAngles, cached.CoverLetterAngles);
        Assert.Equal("ollama", generated.Provider);

        var stored = Assert.Single(db.AiWorkspaceNotes.IgnoreQueryFilters().Where(x => x.JobApplicationId == job.Id && x.NoteType == "focus-plan"));
        Assert.NotEmpty(stored.ResultJson);
    }

    private static CandidateFitDto GetDto(ActionResult<CandidateFitDto> result)
        => (CandidateFitDto)Assert.IsType<OkObjectResult>(result.Result).Value!;

    private static async Task<JobApplication> SeedJobWithCvAsync(JobTrackerContext db)
    {
        var company = new Company { Name = "Acme", OwnerUserId = "user-1" };
        db.Companies.Add(company);
        db.Users.Add(new ApplicationUser
        {
            Id = "user-1",
            UserName = "user@example.test",
            Email = "user@example.test",
            ProfileCvText = "Built .NET APIs and led backend delivery with SQL and Docker.",
            ProfileCvStructureJson = "[]",
        });
        await db.SaveChangesAsync();

        var job = new JobApplication
        {
            JobTitle = "Backend Developer",
            CompanyId = company.Id,
            OwnerUserId = "user-1",
            Description = "Needs .NET, SQL, and Docker experience.",
        };
        db.JobApplications.Add(job);
        await db.SaveChangesAsync();
        return job;
    }

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