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 Moq;
using Xunit;

namespace JobTrackerApi.Tests;

public sealed class CareerProfileControllerTests
{
    private static (CareerProfileController controller, JobTrackerContext db, ApplicationUser user) Build()
    {
        var db = TestHostFactory.CreateInMemoryDb();
        var user = new ApplicationUser { Id = "user-1", Email = "ada@example.com", UserName = "ada" };
        var users = TestHostFactory.CreateUserManager();
        users.Setup(x => x.GetUserAsync(It.IsAny<ClaimsPrincipal>())).ReturnsAsync(user);
        users.Setup(x => x.UpdateAsync(user)).ReturnsAsync(IdentityResult.Success);

        var controller = new CareerProfileController(users.Object, new CareerProfileService(db), db)
        {
            ControllerContext = new ControllerContext
            {
                HttpContext = new DefaultHttpContext
                {
                    User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, "user-1") }, "test")),
                },
            },
        };
        return (controller, db, user);
    }

    private static StructuredCvProfile Sample() => new()
    {
        Contact = { FullName = "Ada Lovelace", Email = "ada@example.com" },
        Jobs = { new StructuredCvJob { Title = "Engineer", Company = "Acme" } },
        Skills = { "C#", "SQL" },
    };

    [Fact]
    public async Task Put_then_get_round_trips_the_profile_and_updates_the_projection()
    {
        var (controller, _, user) = Build();

        var put = await controller.Put(new CareerProfileSaveRequest(Sample(), null), CancellationToken.None);
        Assert.IsType<OkObjectResult>(put.Result);

        // The derived blob projection is kept in sync for legacy readers.
        Assert.Contains("Ada Lovelace", user.ProfileCvStructureJson);

        var get = await controller.Get(CancellationToken.None);
        var dto = Assert.IsType<CareerProfileDto>(Assert.IsType<OkObjectResult>(get.Result).Value);
        Assert.Equal("Ada Lovelace", dto.Profile.Contact.FullName);
        Assert.Single(dto.Profile.Jobs);
        Assert.Equal(new[] { "C#", "SQL" }, dto.Profile.Skills);
    }

    [Fact]
    public async Task Put_then_get_preserves_reviewed_free_form_fields()
    {
        var (controller, _, user) = Build();
        var profile = Sample();
        profile.Contact.Location = "Oslo, Norway and Remote across Europe";
        profile.Contact.Website = "https://example.test/portfolio/ada?view=full";
        profile.Contact.LinkedIn = "https://www.linkedin.com/in/ada-lovelace";
        profile.Jobs[0].Location = "Oslo, Norway and Remote across Europe";
        profile.Jobs[0].Start = "Spring 2020";
        profile.Jobs.Add(new StructuredCvJob { Location = "Remote across Europe", Start = "Before 2020" });
        profile.Languages.Add(new StructuredCvLanguage
        {
            Name = "Norwegian Sign Language",
            Level = "Professional working proficiency",
            Notes = "Used with distributed teams",
        });

        var put = await controller.Put(new CareerProfileSaveRequest(profile, null), CancellationToken.None);
        var saved = Assert.IsType<CareerProfileDto>(Assert.IsType<OkObjectResult>(put.Result).Value).Profile;
        var get = await controller.Get(CancellationToken.None);
        var reloaded = Assert.IsType<CareerProfileDto>(Assert.IsType<OkObjectResult>(get.Result).Value).Profile;

        foreach (var actual in new[] { saved, reloaded })
        {
            Assert.Equal("Oslo, Norway and Remote across Europe", actual.Contact.Location);
            Assert.Equal("https://example.test/portfolio/ada?view=full", actual.Contact.Website);
            Assert.Equal("https://www.linkedin.com/in/ada-lovelace", actual.Contact.LinkedIn);
            Assert.Equal("Oslo, Norway and Remote across Europe", actual.Jobs[0].Location);
            Assert.Equal("Spring 2020", actual.Jobs[0].Start);
            Assert.Equal("Remote across Europe", actual.Jobs[1].Location);
            Assert.Equal("Before 2020", actual.Jobs[1].Start);
            Assert.Equal("Norwegian Sign Language", actual.Languages[0].Name);
            Assert.Equal("Professional working proficiency", actual.Languages[0].Level);
        }

        var projected = StructuredCvProfileJson.DeserializePersisted(user.ProfileCvStructureJson);
        Assert.Equal("https://example.test/portfolio/ada?view=full", projected.Contact.Website);
    }

    [Fact]
    public async Task Get_reports_completeness_with_missing_sections()
    {
        var (controller, _, _) = Build();
        await controller.Put(new CareerProfileSaveRequest(Sample(), null), CancellationToken.None);   // has personal + experience + skills

        var get = await controller.Get(CancellationToken.None);
        var dto = Assert.IsType<CareerProfileDto>(Assert.IsType<OkObjectResult>(get.Result).Value);

        Assert.InRange(dto.Completeness.Percent, 1, 99);
        Assert.Contains("Education", dto.Completeness.Missing);   // not provided
        Assert.DoesNotContain("Experience", dto.Completeness.Missing);
    }

    [Fact]
    public async Task Put_rejects_an_over_limit_profile()
    {
        var (controller, _, _) = Build();
        var huge = new StructuredCvProfile();
        for (var i = 0; i < 500; i++) huge.Skills.Add($"skill-{i}");

        var put = await controller.Put(new CareerProfileSaveRequest(huge, null), CancellationToken.None);

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

    [Fact]
    public async Task Put_rejects_an_over_limit_reviewed_contact_value_instead_of_discarding_it()
    {
        var (controller, _, _) = Build();
        var profile = Sample();
        profile.Contact.Website = $"https://example.test/{new string('a', 600)}";

        var put = await controller.Put(new CareerProfileSaveRequest(profile, null), CancellationToken.None);

        var badRequest = Assert.IsType<BadRequestObjectResult>(put.Result);
        Assert.Equal("A contact field exceeds the allowed length.", badRequest.Value);
    }

    [Fact]
    public async Task Put_accepts_an_empty_work_in_progress_profile()
    {
        var (controller, _, _) = Build();

        var put = await controller.Put(new CareerProfileSaveRequest(new StructuredCvProfile(), null), CancellationToken.None);

        // Completeness, not validation, is what flags an incomplete profile — an empty profile saves.
        Assert.IsType<OkObjectResult>(put.Result);
    }
}
