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 JobApplicationsAuthorizationTests
{
    [Fact]
    public async Task GetHistory_returns_not_found_for_other_users_job()
    {
        var dbName = Guid.NewGuid().ToString();
        await using var ownerDb = CreateDb(dbName, "owner-1");
        var company = new Company { Name = "Acme", OwnerUserId = "owner-1" };
        ownerDb.Companies.Add(company);
        await ownerDb.SaveChangesAsync();

        var job = new JobApplication { JobTitle = "Secret Job", CompanyId = company.Id, OwnerUserId = "owner-1" };
        ownerDb.JobApplications.Add(job);
        await ownerDb.SaveChangesAsync();

        ownerDb.JobEvents.Add(new JobEvent { JobApplicationId = job.Id, Type = "Created", Note = "owner only" });
        await ownerDb.SaveChangesAsync();

        await using var attackerDb = CreateDb(dbName, "other-user");
        var controller = new JobApplicationLifecycleController(attackerDb);

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

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

    [Fact]
    public async Task GetMatchScore_returns_not_found_for_other_users_job()
    {
        var dbName = Guid.NewGuid().ToString();
        await using var ownerDb = CreateDb(dbName, "owner-1");
        var company = new Company { Name = "Acme", OwnerUserId = "owner-1" };
        ownerDb.Companies.Add(company);
        await ownerDb.SaveChangesAsync();
        ownerDb.JobApplications.Add(new JobApplication { JobTitle = "Secret", CompanyId = company.Id, OwnerUserId = "owner-1", Description = "C# .NET" });
        await ownerDb.SaveChangesAsync();
        var jobId = await ownerDb.JobApplications.Select(j => j.Id).FirstAsync();

        await using var attackerDb = CreateDb(dbName, "other-user");
        var result = await CreateController(attackerDb).GetMatchScore(jobId, CancellationToken.None);

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

    [Fact]
    public async Task GetStatusSuggestion_returns_not_found_for_other_users_job()
    {
        var dbName = Guid.NewGuid().ToString();
        await using var ownerDb = CreateDb(dbName, "owner-1");
        var company = new Company { Name = "Acme", OwnerUserId = "owner-1" };
        ownerDb.Companies.Add(company);
        await ownerDb.SaveChangesAsync();
        ownerDb.JobApplications.Add(new JobApplication { JobTitle = "Secret", CompanyId = company.Id, OwnerUserId = "owner-1" });
        await ownerDb.SaveChangesAsync();
        var jobId = await ownerDb.JobApplications.Select(j => j.Id).FirstAsync();

        await using var attackerDb = CreateDb(dbName, "other-user");
        var result = await CreateController(attackerDb).GetStatusSuggestion(jobId, CancellationToken.None);

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

    [Fact]
    public async Task Job_choices_searches_all_owned_rows_without_leaking_another_tenant()
    {
        var dbName = Guid.NewGuid().ToString();
        await using (var ownerDb = CreateDb(dbName, "owner-1"))
        {
            var company = new Company { Name = "Owner company", OwnerUserId = "owner-1" };
            ownerDb.Companies.Add(company);
            for (var index = 0; index < 130; index++)
            {
                ownerDb.JobApplications.Add(new JobApplication
                {
                    JobTitle = index == 0 ? "Historic target role" : $"Recent role {index}",
                    Company = company,
                    OwnerUserId = "owner-1",
                    SavedAt = DateTime.UtcNow.AddDays(index),
                });
            }
            await ownerDb.SaveChangesAsync();
        }
        await using (var otherDb = CreateDb(dbName, "owner-2"))
        {
            var company = new Company { Name = "Other tenant", OwnerUserId = "owner-2" };
            otherDb.JobApplications.Add(new JobApplication
            {
                JobTitle = "Historic target private", Company = company, OwnerUserId = "owner-2",
            });
            await otherDb.SaveChangesAsync();
        }

        await using var searchDb = CreateDb(dbName, "owner-1");
        var result = await CreateController(searchDb).GetChoices("historic target", 20, default);
        var choices = Assert.IsType<List<JobApplicationChoiceDto>>(Assert.IsType<OkObjectResult>(result.Result).Value);

        var choice = Assert.Single(choices);
        Assert.Equal("Historic target role", choice.JobTitle);
        Assert.Equal("Owner company", choice.CompanyName);
    }

    private static JobTrackerContext CreateDb(string dbName, string? userId)
    {
        var options = new DbContextOptionsBuilder<JobTrackerContext>()
            .UseInMemoryDatabase(dbName)
            .Options;
        var currentUser = new Mock<ICurrentUserService>();
        currentUser.SetupGet(service => service.UserId).Returns(userId);
        return new JobTrackerContext(options, currentUser.Object);
    }

    private static JobApplicationsController CreateController(JobTrackerContext db)
    {
        var summarizer = new Mock<ISummarizerService>();
        var users = TestHostFactory.CreateUserManager();
        return new JobApplicationsController(db, summarizer.Object, users.Object)
        {
            ControllerContext = new ControllerContext
            {
                HttpContext = new DefaultHttpContext()
            }
        };
    }
}
