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;

public sealed class JobApplicationsEndpointBehaviorTests
{
    [Fact]
    public async Task Save_application_drafts_updates_cover_letter_and_notes()
    {
        await using var db = CreateDb();
        var company = new Company { Name = "Acme", OwnerUserId = "user-1" };
        db.Companies.Add(company);
        await db.SaveChangesAsync();

        var job = new JobApplication { JobTitle = "Backend Dev", CompanyId = company.Id, OwnerUserId = "user-1" };
        db.JobApplications.Add(job);
        await db.SaveChangesAsync();

        var controller = CreateController(db, "user-1");
        var result = await controller.SaveApplicationDrafts(job.Id, new SaveApplicationDraftsRequest(" Cover letter body ", " Notes body ", null), CancellationToken.None);

        Assert.IsType<NoContentResult>(result);
        var saved = await db.JobApplications.FirstAsync();
        Assert.Equal("Cover letter body", saved.CoverLetterText);
        Assert.Contains("Notes body", saved.Notes);
    }

    [Fact]
    public async Task Generate_application_package_rejects_missing_profile_cv()
    {
        await using var db = CreateDb();
        var company = new Company { Name = "Acme", OwnerUserId = "user-1" };
        db.Companies.Add(company);
        db.Users.Add(new ApplicationUser { Id = "user-1", UserName = "u", Email = "u@example.com" });
        await db.SaveChangesAsync();

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

        var controller = CreateController(db, "user-1");
        var result = await controller.GenerateApplicationPackage(job.Id, null, null, null, CancellationToken.None);

        var badRequest = Assert.IsType<BadRequestObjectResult>(result.Result);
        Assert.Contains("Profile page", badRequest.Value?.ToString());
    }

    [Fact]
    public async Task Status_suggestion_from_latest_inbound_rejection()
    {
        await using var db = CreateDb();
        var company = new Company { Name = "Acme", OwnerUserId = "user-1" };
        db.Companies.Add(company);
        await db.SaveChangesAsync();

        var job = new JobApplication { JobTitle = "Dev", CompanyId = company.Id, OwnerUserId = "user-1", Status = "Applied" };
        db.JobApplications.Add(job);
        await db.SaveChangesAsync();

        db.Correspondences.Add(new Correspondence
        {
            JobApplicationId = job.Id,
            From = "Company",
            Direction = "inbound",
            Subject = "Update",
            Content = "Unfortunately, we have decided not to proceed.",
            Date = DateTime.Now,
        });
        await db.SaveChangesAsync();

        var controller = CreateController(db, "user-1");
        var result = await controller.GetStatusSuggestion(job.Id, CancellationToken.None);

        var ok = Assert.IsType<OkObjectResult>(result.Result);
        var dto = Assert.IsType<StatusSuggestionDto>(ok.Value);
        Assert.True(dto.HasSuggestion);
        Assert.Equal("Rejected", dto.SuggestedStatus);
    }

    [Fact]
    public async Task Status_suggestion_suppressed_when_already_in_stage()
    {
        await using var db = CreateDb();
        var company = new Company { Name = "Acme", OwnerUserId = "user-1" };
        db.Companies.Add(company);
        await db.SaveChangesAsync();

        var job = new JobApplication { JobTitle = "Dev", CompanyId = company.Id, OwnerUserId = "user-1", Status = "Rejected" };
        db.JobApplications.Add(job);
        await db.SaveChangesAsync();

        db.Correspondences.Add(new Correspondence
        {
            JobApplicationId = job.Id,
            From = "Company",
            Direction = "inbound",
            Content = "Unfortunately, we will not be moving forward.",
            Date = DateTime.Now,
        });
        await db.SaveChangesAsync();

        var controller = CreateController(db, "user-1");
        var result = await controller.GetStatusSuggestion(job.Id, CancellationToken.None);

        var ok = Assert.IsType<OkObjectResult>(result.Result);
        var dto = Assert.IsType<StatusSuggestionDto>(ok.Value);
        Assert.False(dto.HasSuggestion);
    }

    [Fact]
    public async Task Match_score_scores_job_against_profile_cv()
    {
        await using var db = CreateDb();
        var company = new Company { Name = "Acme", OwnerUserId = "user-1" };
        db.Companies.Add(company);
        db.Users.Add(new ApplicationUser
        {
            Id = "user-1",
            UserName = "u",
            Email = "u@example.com",
            ProfileCvText = "Backend engineer skilled in C#, .NET, SQL and Docker. Built REST APIs.",
        });
        await db.SaveChangesAsync();

        var job = new JobApplication
        {
            JobTitle = "Senior C# Backend Developer",
            CompanyId = company.Id,
            OwnerUserId = "user-1",
            Description = "We need strong C#, .NET, SQL, Docker and REST API experience.",
        };
        db.JobApplications.Add(job);
        await db.SaveChangesAsync();

        var controller = CreateController(db, "user-1");
        var result = await controller.GetMatchScore(job.Id, CancellationToken.None);

        var ok = Assert.IsType<OkObjectResult>(result.Result);
        var dto = Assert.IsType<MatchScoreDto>(ok.Value);
        Assert.True(dto.HasEnoughSignal);
        Assert.True(dto.Score >= 75, $"expected strong score, got {dto.Score}");
        Assert.Contains("C#", dto.MatchedKeywords);
    }

    [Fact]
    public async Task Match_score_requires_profile_cv()
    {
        await using var db = CreateDb();
        var company = new Company { Name = "Acme", OwnerUserId = "user-1" };
        db.Companies.Add(company);
        db.Users.Add(new ApplicationUser { Id = "user-1", UserName = "u", Email = "u@example.com" });
        await db.SaveChangesAsync();

        var job = new JobApplication { JobTitle = "Dev", CompanyId = company.Id, OwnerUserId = "user-1", Description = "C# .NET" };
        db.JobApplications.Add(job);
        await db.SaveChangesAsync();

        var controller = CreateController(db, "user-1");
        var result = await controller.GetMatchScore(job.Id, CancellationToken.None);

        Assert.IsType<BadRequestObjectResult>(result.Result);
    }

    [Fact]
    public async Task Create_normalizes_structured_salary()
    {
        await using var db = CreateDb();
        var company = new Company { Name = "Acme", OwnerUserId = "user-1" };
        db.Companies.Add(company);
        await db.SaveChangesAsync();

        var controller = CreateController(db, "user-1");
        var request = new CreateJobApplicationRequest(
            JobTitle: "Backend Dev",
            CompanyId: company.Id,
            Status: null,
            Location: null,
            Salary: "60-70k",
            SalaryMin: 70000m,   // min > max on purpose: normalization swaps them
            SalaryMax: 60000m,
            SalaryCurrency: " nok ",
            SalaryPeriod: "YEAR",
            NextAction: null,
            FollowUpAt: null,
            Notes: null,
            Description: null,
            TranslatedDescription: null,
            DescriptionLanguage: null,
            Tags: null,
            Deadline: null,
            CoverLetterText: null,
            JobUrl: null,
            DateApplied: null,
            FeedbackRequestedAt: null,
            Source: "NAV",
            CountryCode: "no");

        var result = await controller.Create(request, CancellationToken.None);

        Assert.NotNull(result);
        var created = Assert.IsType<CreatedAtActionResult>(result.Result);
        Assert.IsType<JobApplicationDto>(created.Value);
        var saved = await db.JobApplications.FirstAsync();
        Assert.Equal(60000m, saved.SalaryMin);
        Assert.Equal(70000m, saved.SalaryMax);
        Assert.Equal("NOK", saved.SalaryCurrency);
        Assert.Equal("year", saved.SalaryPeriod);
        Assert.Equal("60-70k", saved.Salary);
        var opportunity = await db.Jobs.SingleAsync();
        Assert.Equal(opportunity.Id, saved.JobId);
        Assert.Equal("nav", opportunity.Source);
        Assert.Equal("NO", opportunity.CountryCode);
        Assert.Equal(saved.JobTitle, opportunity.JobTitle);
    }

    [Fact]
    public async Task Update_normalizes_salary_and_preserves_the_separate_application_answer()
    {
        await using var db = CreateDb();
        var company = new Company { Name = "Acme", OwnerUserId = "user-1" };
        db.Companies.Add(company);
        await db.SaveChangesAsync();

        var job = new JobApplication
        {
            JobTitle = "Backend Dev",
            CompanyId = company.Id,
            OwnerUserId = "user-1",
            SalaryMin = 50000m,
            SalaryMax = 60000m,
            SalaryCurrency = "NOK",
            SalaryPeriod = "year",
            Notes = "Old human note\n\n<<<APPLICATION_ANSWER_DRAFT>>>\nSaved answer\n<<<END_APPLICATION_ANSWER_DRAFT>>>",
            Job = new Job { CompanyId = company.Id, JobTitle = "Backend Dev", Source = "nav", CountryCode = "NO" },
        };
        db.JobApplications.Add(job);
        await db.SaveChangesAsync();

        var controller = CreateController(db, "user-1");
        var request = new UpdateJobApplicationRequest(
            JobTitle: "Backend Dev",
            CompanyId: company.Id,
            Status: "Applied",
            ResponseReceived: false,
            ResponseDate: null,
            Location: null,
            Salary: null,
            SalaryMin: -5m,
            SalaryMax: null,
            SalaryCurrency: "",
            SalaryPeriod: "fortnight",
            NextAction: null,
            FollowUpAt: null,
            Notes: "Updated human note",
            Description: null,
            TranslatedDescription: null,
            DescriptionLanguage: null,
            Tags: null,
            Deadline: null,
            CoverLetterText: null,
            JobUrl: null,
            DateApplied: null,
            FeedbackRequestedAt: null,
            StatusChangedAt: null);

        var result = await controller.Update(job.Id, request, CancellationToken.None);

        Assert.IsType<NoContentResult>(result);
        var saved = await db.JobApplications.FirstAsync();
        Assert.Null(saved.SalaryMin);
        Assert.Null(saved.SalaryMax);
        Assert.Null(saved.SalaryCurrency);
        Assert.Null(saved.SalaryPeriod);
        Assert.Equal("Updated human note", JobApplicationHelpers.RemoveSavedApplicationAnswerDraft(saved.Notes));
        Assert.Equal("Saved answer", JobApplicationHelpers.ExtractSavedApplicationAnswerDraft(saved.Notes));
        var opportunity = await db.Jobs.SingleAsync();
        Assert.Equal(saved.JobTitle, opportunity.JobTitle);
        Assert.Null(opportunity.SalaryMin);
        Assert.Null(opportunity.SalaryPeriod);
        Assert.Equal("nav", opportunity.Source);
        Assert.Equal("NO", opportunity.CountryCode);
    }

    [Fact]
    public async Task Update_refreshes_analysis_only_when_the_advert_materially_changes()
    {
        await using var db = CreateDb();
        var company = new Company { Name = "Acme", OwnerUserId = "user-1" };
        db.Companies.Add(company);
        await db.SaveChangesAsync();

        var originalDescription = "Build and operate C# services with SQL, Docker and Azure. Work with product teams to deliver reliable APIs.";
        var job = new JobApplication
        {
            JobTitle = "Backend Developer",
            CompanyId = company.Id,
            OwnerUserId = "user-1",
            Description = originalDescription,
            ShortSummary = "Old analysis",
        };
        db.JobApplications.Add(job);
        await db.SaveChangesAsync();

        var summarizer = new Mock<ISummarizerService>();
        summarizer.Setup(service => service.SummarizeAsync(It.IsAny<string>(), 160, 60)).ReturnsAsync("Fresh analysis");
        var user = new ApplicationUser { Id = "user-1", UserName = "user", AiEnabled = true };
        var controller = CreateController(db, "user-1", summarizer, user, new[] { "Premium" });
        var request = new UpdateJobApplicationRequest(
            JobTitle: "Backend Developer",
            CompanyId: company.Id,
            Status: "Applied",
            ResponseReceived: false,
            ResponseDate: null,
            Location: null,
            Salary: null,
            SalaryMin: null,
            SalaryMax: null,
            SalaryCurrency: null,
            SalaryPeriod: null,
            NextAction: null,
            FollowUpAt: null,
            Notes: null,
            Description: "Lead a Kubernetes platform team building Go services for machine-learning workloads and distributed event streaming.",
            TranslatedDescription: null,
            DescriptionLanguage: "en",
            Tags: null,
            Deadline: null,
            CoverLetterText: null,
            JobUrl: null,
            DateApplied: null,
            FeedbackRequestedAt: null,
            StatusChangedAt: null);

        Assert.IsType<NoContentResult>(await controller.Update(job.Id, request, CancellationToken.None));

        Assert.Equal("Fresh analysis", (await db.JobApplications.SingleAsync()).ShortSummary);
        summarizer.Verify(service => service.SummarizeAsync(It.IsAny<string>(), 160, 60), Times.Once);
        Assert.Contains(await db.JobEvents.ToListAsync(), item => item.Type == "AiRefreshed" && item.Note!.Contains("automatically"));

        request = request with { Description = request.Description + "  " };
        Assert.IsType<NoContentResult>(await controller.Update(job.Id, request, CancellationToken.None));
        summarizer.Verify(service => service.SummarizeAsync(It.IsAny<string>(), 160, 60), Times.Once);
    }

    private static JobApplicationsController CreateController(
        JobTrackerContext db,
        string userId,
        Mock<ISummarizerService>? summarizerOverride = null,
        ApplicationUser? lookupUser = null,
        IReadOnlyList<string>? roles = null)
    {
        var summarizer = summarizerOverride ?? new Mock<ISummarizerService>();
        summarizer.Setup(x => x.SummarizeSectionAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>())).ReturnsAsync("generated text");
        var users = TestHostFactory.CreateUserManager(lookupUser);
        if (lookupUser is not null)
            users.Setup(manager => manager.GetRolesAsync(lookupUser)).ReturnsAsync(roles?.ToList() ?? new List<string>());

        var controller = new JobApplicationsController(db, summarizer.Object, users.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()
    {
        return TestHostFactory.CreateInMemoryDb();
    }
}
