using Books.Api.Domain.Companies;
using Books.Api.EventFlow.Repositories;
using Books.Api.Tests.Helpers;
using Books.Api.Tests.Infrastructure;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;

namespace Books.Api.Tests.GraphQL;

/// <summary>
/// Integration tests for Company GraphQL operations.
/// Each test class runs with its own isolated database.
/// </summary>
[Trait("Category", "Integration")]
public class CompanyGraphQLTests(TestWebApplicationFactory factory)
    : IntegrationTestBase(factory)
{
    [Fact]
    public async Task Query_Companies_ReturnsEmptyList_WhenNoCompaniesExist()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);

        // Act
        var response = await graphqlClient.QueryAsync<CompaniesResponse>("""
            query {
                companies {
                    id
                    name
                }
            }
            """);

        // Assert
        response.EnsureNoErrors();
        response.Data.Should().NotBeNull();
        response.Data!.Companies.Should().BeEmpty();
    }

    [Fact]
    public async Task Mutation_CreateCompany_CreatesCompanySuccessfully()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);

        // Act
        var response = await graphqlClient.MutateAsync<CreateCompanyResponse>("""
            mutation CreateCompany($input: CreateCompanyInput!) {
                createCompany(input: $input) {
                    id
                    name
                    cvr
                    country
                    currency
                    fiscalYearStartMonth
                }
            }
            """,
            new
            {
                input = new
                {
                    name = "Test Virksomhed A/S",
                    cvr = "12345678",
                    country = "DK",
                    currency = "DKK",
                    fiscalYearStartMonth = 1
                }
            });

        // Assert
        response.EnsureNoErrors();
        response.Data.Should().NotBeNull();
        response.Data!.CreateCompany.Should().NotBeNull();
        response.Data.CreateCompany!.Name.Should().Be("Test Virksomhed A/S");
        response.Data.CreateCompany.Cvr.Should().Be("12345678");
        response.Data.CreateCompany.Country.Should().Be("DK");
        response.Data.CreateCompany.Currency.Should().Be("DKK");
        response.Data.CreateCompany.FiscalYearStartMonth.Should().Be(1);
    }

    [Fact]
    public async Task Query_Company_ReturnsCompany_AfterCreation()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);

        // Create a company first
        var createResponse = await graphqlClient.MutateAsync<CreateCompanyResponse>("""
            mutation CreateCompany($input: CreateCompanyInput!) {
                createCompany(input: $input) {
                    id
                    name
                }
            }
            """,
            new
            {
                input = new
                {
                    name = "Query Test Virksomhed",
                    cvr = "87654321"
                }
            });

        createResponse.EnsureNoErrors();
        var companyId = createResponse.Data!.CreateCompany!.Id;

        // Act - Query the company by ID (with eventual consistency)
        var company = await Eventually.GetAsync(async () =>
        {
            var response = await graphqlClient.QueryAsync<CompanyResponse>("""
                query GetCompany($id: ID!) {
                    company(id: $id) {
                        id
                        name
                        cvr
                    }
                }
                """,
                new { id = companyId });

            return response.Data?.Company;
        });

        // Assert
        company.Should().NotBeNull();
        company!.Name.Should().Be("Query Test Virksomhed");
        company.Cvr.Should().Be("87654321");
    }

    [Fact]
    public async Task Query_Companies_ReturnsAllCompanies_AfterMultipleCreations()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);

        // Create multiple companies with unique names for this test
        var company1Name = $"ListTest-Virksomhed-{Guid.NewGuid():N}";
        var company2Name = $"ListTest-Virksomhed-{Guid.NewGuid():N}";

        await graphqlClient.MutateAsync<CreateCompanyResponse>($$"""
            mutation { createCompany(input: { name: "{{company1Name}}" }) { id } }
            """);

        await graphqlClient.MutateAsync<CreateCompanyResponse>($$"""
            mutation { createCompany(input: { name: "{{company2Name}}" }) { id } }
            """);

        // Act - Wait for eventual consistency (at least 2 companies with our prefix)
        var companies = await Eventually.GetAsync(
            async () =>
            {
                var response = await graphqlClient.QueryAsync<CompaniesResponse>("""
                    query {
                        companies {
                            id
                            name
                        }
                    }
                    """);
                var allCompanies = response.Data?.Companies ?? [];
                var ourCompanies = allCompanies.Where(c => c.Name.StartsWith("ListTest-")).ToList();
                return ourCompanies.Count >= 2 ? ourCompanies : null;
            });

        // Assert - verify our specific companies are present
        companies.Should().Contain(c => c.Name == company1Name);
        companies.Should().Contain(c => c.Name == company2Name);
    }

    [Fact]
    public async Task Mutation_UpdateCompany_UpdatesCompanySuccessfully()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);

        // Create a company first
        var createResponse = await graphqlClient.MutateAsync<CreateCompanyResponse>("""
            mutation { createCompany(input: { name: "Original Name" }) { id } }
            """);

        createResponse.EnsureNoErrors();
        var companyId = createResponse.Data!.CreateCompany!.Id;

        // Wait for the company to be created
        await Eventually.GetAsync(async () =>
        {
            var repository = GetService<ICompanyRepository>();
            return await repository.GetByIds([CompanyId.With(companyId)]);
        });

        // Act - Update the company
        var updateResponse = await graphqlClient.MutateAsync<UpdateCompanyResponse>("""
            mutation UpdateCompany($id: ID!, $input: UpdateCompanyInput!) {
                updateCompany(id: $id, input: $input) {
                    id
                    name
                    cvr
                }
            }
            """,
            new
            {
                id = companyId,
                input = new
                {
                    name = "Updated Name",
                    cvr = "11111111"
                }
            });

        // Assert
        updateResponse.EnsureNoErrors();

        // Wait for eventual consistency and verify update
        var updatedCompany = await Eventually.GetAsync(async () =>
        {
            var repository = GetService<ICompanyRepository>();
            var companies = await repository.GetByIds([CompanyId.With(companyId)]);
            var company = companies.First();
            return company?.Name == "Updated Name" ? company : null;
        });

        updatedCompany.Should().NotBeNull();
        updatedCompany!.Name.Should().Be("Updated Name");
        updatedCompany.Cvr.Should().Be("11111111");
    }

    // Response DTOs for deserialization
    private class CompaniesResponse
    {
        public List<CompanyDto> Companies { get; set; } = [];
    }

    private class CompanyResponse
    {
        public CompanyDto? Company { get; set; }
    }

    private class CreateCompanyResponse
    {
        public CompanyDto? CreateCompany { get; set; }
    }

    private class UpdateCompanyResponse
    {
        public CompanyDto? UpdateCompany { get; set; }
    }

    private class CompanyDto
    {
        public string Id { get; set; } = string.Empty;
        public string Name { get; set; } = string.Empty;
        public string? Cvr { get; set; }
        public string? Country { get; set; }
        public string? Currency { get; set; }
        public int? FiscalYearStartMonth { get; set; }
    }
}
