using Books.Api.EventFlow.Repositories;
using Books.Api.Tests.Helpers;
using Books.Api.Tests.Infrastructure;
using AwesomeAssertions;
using Microsoft.Extensions.DependencyInjection;

namespace Books.Api.Tests.GraphQL;

/// <summary>
/// Integration tests for FiscalYear GraphQL operations.
/// </summary>
[Trait("Category", "Integration")]
public class FiscalYearGraphQLTests(TestWebApplicationFactory factory)
    : IntegrationTestBase(factory)
{
    [Fact]
    public async Task Query_FiscalYears_ReturnsEmptyList_WhenNoFiscalYearsExist()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY Empty Test");

        // Act
        var response = await graphqlClient.QueryAsync<FiscalYearsResponse>("""
            query FiscalYears($companyId: ID!) {
                fiscalYears(companyId: $companyId) {
                    id
                    name
                }
            }
            """,
            new { companyId });

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

    [Fact]
    public async Task Mutation_CreateFiscalYear_CreatesFiscalYearSuccessfully()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY Create Test");

        // Act
        var response = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) {
                    id
                    companyId
                    name
                    startDate
                    endDate
                    status
                    openingBalancePosted
                }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "2024",
                    startDate = "2024-01-01T00:00:00",
                    endDate = "2025-01-01T00:00:00"  // Exactly 12 months
                }
            });

        // Assert
        response.EnsureNoErrors();
        response.Data.Should().NotBeNull();
        response.Data!.CreateFiscalYear.Should().NotBeNull();
        response.Data.CreateFiscalYear!.Name.Should().Be("2024");
        response.Data.CreateFiscalYear.Status.Should().Be("open");
        response.Data.CreateFiscalYear.OpeningBalancePosted.Should().BeFalse();
    }

    [Fact]
    public async Task Mutation_CloseFiscalYear_ClosesFiscalYearSuccessfully()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY Close Test");

        var createResponse = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) { id status }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "2023",
                    startDate = "2023-01-01T00:00:00",
                    endDate = "2024-01-01T00:00:00"  // Exactly 12 months
                }
            });

        createResponse.EnsureNoErrors();
        var fiscalYearId = createResponse.Data!.CreateFiscalYear!.Id;

        await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            return await repo.GetByIdAsync(fiscalYearId);
        });

        // Act
        var closeResponse = await graphqlClient.MutateAsync<CloseFiscalYearResponse>("""
            mutation CloseFiscalYear($id: ID!) {
                closeFiscalYear(id: $id) {
                    id
                    status
                    closingDate
                    closedBy
                }
            }
            """,
            new { id = fiscalYearId });

        // Assert
        closeResponse.EnsureNoErrors();

        var closedFiscalYear = await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            var fy = await repo.GetByIdAsync(fiscalYearId);
            return fy?.Status == "closed" ? fy : null;
        });

        closedFiscalYear.Should().NotBeNull();
        closedFiscalYear!.Status.Should().Be("closed");
        closedFiscalYear.ClosedBy.Should().NotBeNullOrEmpty();
    }

    [Fact]
    public async Task Mutation_ReopenFiscalYear_ReopensFiscalYearSuccessfully()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY Reopen Test");

        var createResponse = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) { id }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "2022",
                    startDate = "2022-01-01T00:00:00",
                    endDate = "2023-01-01T00:00:00"  // Exactly 12 months
                }
            });

        var fiscalYearId = createResponse.Data!.CreateFiscalYear!.Id;

        await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            return await repo.GetByIdAsync(fiscalYearId);
        });

        // Close first
        await graphqlClient.MutateAsync<CloseFiscalYearResponse>(
            $"mutation {{ closeFiscalYear(id: \"{fiscalYearId}\") {{ id }} }}");

        await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            var fy = await repo.GetByIdAsync(fiscalYearId);
            return fy?.Status == "closed" ? fy : null;
        });

        // Act
        var reopenResponse = await graphqlClient.MutateAsync<ReopenFiscalYearResponse>("""
            mutation ReopenFiscalYear($id: ID!) {
                reopenFiscalYear(id: $id) {
                    id
                    status
                }
            }
            """,
            new { id = fiscalYearId });

        // Assert
        reopenResponse.EnsureNoErrors();

        var reopenedFiscalYear = await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            var fy = await repo.GetByIdAsync(fiscalYearId);
            return fy?.Status == "open" ? fy : null;
        });

        reopenedFiscalYear.Should().NotBeNull();
        reopenedFiscalYear!.Status.Should().Be("open");
    }

    [Fact]
    public async Task Mutation_LockFiscalYear_LocksFiscalYearSuccessfully()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY Lock Test");

        var createResponse = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) { id }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "2021",
                    startDate = "2021-01-01T00:00:00",
                    endDate = "2022-01-01T00:00:00"  // Exactly 12 months
                }
            });

        var fiscalYearId = createResponse.Data!.CreateFiscalYear!.Id;

        await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            return await repo.GetByIdAsync(fiscalYearId);
        });

        // Must close before locking
        await graphqlClient.MutateAsync<CloseFiscalYearResponse>(
            $"mutation {{ closeFiscalYear(id: \"{fiscalYearId}\") {{ id }} }}");

        await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            var fy = await repo.GetByIdAsync(fiscalYearId);
            return fy?.Status == "closed" ? fy : null;
        });

        // Act
        var lockResponse = await graphqlClient.MutateAsync<LockFiscalYearResponse>("""
            mutation LockFiscalYear($id: ID!) {
                lockFiscalYear(id: $id) {
                    id
                    status
                }
            }
            """,
            new { id = fiscalYearId });

        // Assert
        lockResponse.EnsureNoErrors();

        var lockedFiscalYear = await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            var fy = await repo.GetByIdAsync(fiscalYearId);
            return fy?.Status == "locked" ? fy : null;
        });

        lockedFiscalYear.Should().NotBeNull();
        lockedFiscalYear!.Status.Should().Be("locked");
    }

    [Fact]
    public async Task Mutation_CreateFiscalYear_FailsWithInvalidDuration()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY Invalid Duration Test");

        // Act - Try to create fiscal year with only 3 months (must be 12 for standard, 6-18 for first/reorg)
        var response = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) { id }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "Q1 2024",
                    startDate = "2024-01-01T00:00:00",
                    endDate = "2024-03-31T00:00:00" // Only 3 months - invalid
                }
            });

        // Assert - Domain validation error is wrapped by GraphQL
        response.HasErrors.Should().BeTrue();
        var errorDetails = response.Errors!.First().Extensions?.GetValueOrDefault("details")?.ToString() ?? "";
        var hasValidationError = response.Errors!.Any(e =>
            e.Message.Contains("INVALID_DURATION") ||
            e.Message.Contains("INVALID_STANDARD_DURATION") ||
            errorDetails.Contains("INVALID_DURATION") ||
            errorDetails.Contains("INVALID_STANDARD_DURATION") ||
            errorDetails.Contains("12 months"));
        hasValidationError.Should().BeTrue("Expected an error related to invalid fiscal year duration");
    }

    [Fact]
    public async Task Mutation_ReopenFiscalYear_FailsWhenLocked()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY Cannot Reopen Locked Test");

        var createResponse = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) { id }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "2020",
                    startDate = "2020-01-01T00:00:00",
                    endDate = "2021-01-01T00:00:00"  // Exactly 12 months
                }
            });

        var fiscalYearId = createResponse.Data!.CreateFiscalYear!.Id;

        await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            return await repo.GetByIdAsync(fiscalYearId);
        });

        // Close and lock
        await graphqlClient.MutateAsync<CloseFiscalYearResponse>(
            $"mutation {{ closeFiscalYear(id: \"{fiscalYearId}\") {{ id }} }}");
        await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            var fy = await repo.GetByIdAsync(fiscalYearId);
            return fy?.Status == "closed" ? fy : null;
        });

        await graphqlClient.MutateAsync<LockFiscalYearResponse>(
            $"mutation {{ lockFiscalYear(id: \"{fiscalYearId}\") {{ id }} }}");
        await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            var fy = await repo.GetByIdAsync(fiscalYearId);
            return fy?.Status == "locked" ? fy : null;
        });

        // Act - Try to reopen locked fiscal year
        var reopenResponse = await graphqlClient.MutateAsync<ReopenFiscalYearResponse>("""
            mutation ReopenFiscalYear($id: ID!) {
                reopenFiscalYear(id: $id) { id status }
            }
            """,
            new { id = fiscalYearId });

        // Assert - Domain validation error is wrapped by GraphQL
        reopenResponse.HasErrors.Should().BeTrue();
        var errorDetails = reopenResponse.Errors!.First().Extensions?.GetValueOrDefault("details")?.ToString() ?? "";
        var hasLockError = reopenResponse.Errors!.Any(e =>
            e.Message.Contains("FISCAL_YEAR_LOCKED") ||
            errorDetails.Contains("FISCAL_YEAR_LOCKED") ||
            errorDetails.Contains("Locked fiscal years cannot be reopened"));
        hasLockError.Should().BeTrue("Expected an error related to locked fiscal year");
    }

    [Fact]
    public async Task Mutation_CreateFiscalYear_FailsWithOverlappingDates()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY Overlap Test");

        // Create first fiscal year (12 months as required for standard)
        var firstResponse = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) { id }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "2024",
                    startDate = "2024-01-01T00:00:00",
                    endDate = "2025-01-01T00:00:00"  // Exactly 12 months
                }
            });

        firstResponse.EnsureNoErrors();

        // Wait for first fiscal year to be created
        await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            return await repo.GetByIdAsync(firstResponse.Data!.CreateFiscalYear!.Id);
        });

        // Act - Try to create overlapping fiscal year (also 12 months)
        var overlapResponse = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) { id }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "2024 Overlap",
                    startDate = "2024-06-01T00:00:00",
                    endDate = "2025-06-01T00:00:00"  // Overlaps with first
                }
            });

        // Assert
        overlapResponse.HasErrors.Should().BeTrue();
        var errorDetails = overlapResponse.Errors!.First().Extensions?.GetValueOrDefault("details")?.ToString() ?? "";
        var hasOverlapError = overlapResponse.Errors!.Any(e =>
            e.Message.Contains("OVERLAPPING_FISCAL_YEAR") ||
            errorDetails.Contains("OVERLAPPING_FISCAL_YEAR") ||
            errorDetails.Contains("overlaps"));
        hasOverlapError.Should().BeTrue("Expected an error related to overlapping fiscal years");
    }

    [Fact]
    public async Task Mutation_CreateFiscalYear_IsDeterministic()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY Deterministic Test");

        // Act - Create same fiscal year twice
        var firstResponse = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) { id }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "2025",
                    startDate = "2025-01-01T00:00:00",
                    endDate = "2026-01-01T00:00:00"  // Exactly 12 months
                }
            });

        firstResponse.EnsureNoErrors();
        var firstId = firstResponse.Data!.CreateFiscalYear!.Id;

        // Second attempt should fail (due to existing fiscal year with same ID)
        var secondResponse = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) { id }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "2025 Second Attempt",
                    startDate = "2025-01-01T00:00:00",
                    endDate = "2025-12-31T00:00:00"
                }
            });

        // Assert - Second attempt should fail with overlap error
        // (because same dates = same deterministic ID AND overlap detection)
        secondResponse.HasErrors.Should().BeTrue();
    }

    [Fact]
    public async Task Mutation_LockFiscalYear_FailsWhenNotClosed()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY Lock Open Test");

        var createResponse = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) { id status }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "2019",
                    startDate = "2019-01-01T00:00:00",
                    endDate = "2020-01-01T00:00:00"  // Exactly 12 months
                }
            });

        createResponse.EnsureNoErrors();
        var fiscalYearId = createResponse.Data!.CreateFiscalYear!.Id;

        await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            return await repo.GetByIdAsync(fiscalYearId);
        });

        // Act - Try to lock an open fiscal year (not closed)
        var lockResponse = await graphqlClient.MutateAsync<LockFiscalYearResponse>("""
            mutation LockFiscalYear($id: ID!) {
                lockFiscalYear(id: $id) { id status }
            }
            """,
            new { id = fiscalYearId });

        // Assert
        lockResponse.HasErrors.Should().BeTrue();
        var errorDetails = lockResponse.Errors!.First().Extensions?.GetValueOrDefault("details")?.ToString() ?? "";
        (lockResponse.Errors!.Any(e => e.Message.Contains("MUST_BE_CLOSED")) ||
         errorDetails.Contains("MUST_BE_CLOSED") ||
         errorDetails.Contains("must be closed")).Should().BeTrue();
    }

    [Fact]
    public async Task Mutation_LockFiscalYear_FailsWhenAlreadyLocked()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY Double Lock Test");

        var createResponse = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) { id }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "2018",
                    startDate = "2018-01-01T00:00:00",
                    endDate = "2019-01-01T00:00:00"  // Exactly 12 months
                }
            });

        var fiscalYearId = createResponse.Data!.CreateFiscalYear!.Id;

        await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            return await repo.GetByIdAsync(fiscalYearId);
        });

        // Close and lock first
        await graphqlClient.MutateAsync<CloseFiscalYearResponse>(
            $"mutation {{ closeFiscalYear(id: \"{fiscalYearId}\") {{ id }} }}");
        await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            var fy = await repo.GetByIdAsync(fiscalYearId);
            return fy?.Status == "closed" ? fy : null;
        });

        await graphqlClient.MutateAsync<LockFiscalYearResponse>(
            $"mutation {{ lockFiscalYear(id: \"{fiscalYearId}\") {{ id }} }}");
        await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            var fy = await repo.GetByIdAsync(fiscalYearId);
            return fy?.Status == "locked" ? fy : null;
        });

        // Act - Try to lock again
        var lockResponse = await graphqlClient.MutateAsync<LockFiscalYearResponse>("""
            mutation LockFiscalYear($id: ID!) {
                lockFiscalYear(id: $id) { id status }
            }
            """,
            new { id = fiscalYearId });

        // Assert
        lockResponse.HasErrors.Should().BeTrue();
        var errorDetails = lockResponse.Errors!.First().Extensions?.GetValueOrDefault("details")?.ToString() ?? "";
        (lockResponse.Errors!.Any(e => e.Message.Contains("ALREADY_LOCKED")) ||
         errorDetails.Contains("ALREADY_LOCKED") ||
         errorDetails.Contains("already locked")).Should().BeTrue();
    }

    [Fact]
    public async Task Mutation_CloseFiscalYear_FailsWhenAlreadyClosed()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY Double Close Test");

        var createResponse = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) { id }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "2017",
                    startDate = "2017-01-01T00:00:00",
                    endDate = "2018-01-01T00:00:00"  // Exactly 12 months
                }
            });

        var fiscalYearId = createResponse.Data!.CreateFiscalYear!.Id;

        await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            return await repo.GetByIdAsync(fiscalYearId);
        });

        // Close first
        await graphqlClient.MutateAsync<CloseFiscalYearResponse>(
            $"mutation {{ closeFiscalYear(id: \"{fiscalYearId}\") {{ id }} }}");
        await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            var fy = await repo.GetByIdAsync(fiscalYearId);
            return fy?.Status == "closed" ? fy : null;
        });

        // Act - Try to close again
        var closeResponse = await graphqlClient.MutateAsync<CloseFiscalYearResponse>("""
            mutation CloseFiscalYear($id: ID!) {
                closeFiscalYear(id: $id) { id status }
            }
            """,
            new { id = fiscalYearId });

        // Assert
        closeResponse.HasErrors.Should().BeTrue();
        var errorDetails = closeResponse.Errors!.First().Extensions?.GetValueOrDefault("details")?.ToString() ?? "";
        (closeResponse.Errors!.Any(e => e.Message.Contains("ALREADY_CLOSED")) ||
         errorDetails.Contains("ALREADY_CLOSED") ||
         errorDetails.Contains("already closed")).Should().BeTrue();
    }

    [Fact]
    public async Task Mutation_CreateFiscalYear_FailsWhenEndDateBeforeStartDate()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY Invalid Date Range Test");

        // Act - Try to create fiscal year with end date before start date
        var response = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) { id }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "Invalid Dates",
                    startDate = "2024-12-31T00:00:00",
                    endDate = "2024-01-01T00:00:00" // End before start
                }
            });

        // Assert
        response.HasErrors.Should().BeTrue();
        var errorDetails = response.Errors!.First().Extensions?.GetValueOrDefault("details")?.ToString() ?? "";
        (response.Errors!.Any(e => e.Message.Contains("INVALID_DATE_RANGE")) ||
         errorDetails.Contains("INVALID_DATE_RANGE") ||
         errorDetails.Contains("End date must be after start date")).Should().BeTrue();
    }

    [Fact]
    public async Task Mutation_CreateFiscalYear_AcceptsExactly6Months_WhenFirstFiscalYear()
    {
        // Arrange - Per Årsregnskabsloven §15, first fiscal year can be 6-18 months
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY 6 Months First Test");

        // Act - Create fiscal year with exactly 6 months (minimum allowed for first year)
        // Domain calculates: (endYear - startYear) * 12 + (endMonth - startMonth)
        // Jan to Jul = (0 * 12) + (7 - 1) = 6
        var response = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) {
                    id
                    name
                    startDate
                    endDate
                }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "First Year H1 2024",
                    startDate = "2024-01-01T00:00:00",
                    endDate = "2024-07-01T00:00:00", // 6 months by domain calculation
                    isFirstFiscalYear = true
                }
            });

        // Assert - Should succeed because isFirstFiscalYear allows 6-18 months
        response.EnsureNoErrors();
        response.Data!.CreateFiscalYear!.Name.Should().Be("First Year H1 2024");
    }

    [Fact]
    public async Task Mutation_CreateFiscalYear_AcceptsExactly18Months_WhenReorganization()
    {
        // Arrange - Per Årsregnskabsloven §15, reorganization allows up to 18 months
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY 18 Months Reorg Test");

        // Act - Create fiscal year with exactly 18 months (maximum allowed for reorganization)
        // Domain calculates: (endYear - startYear) * 12 + (endMonth - startMonth)
        // Jan 2024 to Jul 2025 = (1 * 12) + (7 - 1) = 18
        var response = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) {
                    id
                    name
                }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "Reorganization 2024-2025",
                    startDate = "2024-01-01T00:00:00",
                    endDate = "2025-07-01T00:00:00", // 18 months by domain calculation
                    isReorganization = true
                }
            });

        // Assert - Should succeed because isReorganization allows 6-18 months
        response.EnsureNoErrors();
        response.Data!.CreateFiscalYear!.Name.Should().Be("Reorganization 2024-2025");
    }

    #region Danish Compliance: Fiscal Year Duration Tests (Årsregnskabsloven §15)

    [Fact]
    public async Task Mutation_CreateFiscalYear_RequiresExactly12MonthsForStandardYear()
    {
        // Arrange - Per Årsregnskabsloven §15, standard fiscal years must be 12 months
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY Standard Duration Test");

        // Act - Try to create 6-month fiscal year WITHOUT isFirstFiscalYear flag
        var response = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) { id }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "Invalid Standard 6 Months",
                    startDate = "2024-01-01T00:00:00",
                    endDate = "2024-07-01T00:00:00" // 6 months - invalid for standard year
                    // NOT setting isFirstFiscalYear or isReorganization
                }
            });

        // Assert - Should fail because standard years must be 12 months
        response.HasErrors.Should().BeTrue();
        var errorDetails = response.Errors!.First().Extensions?.GetValueOrDefault("details")?.ToString() ?? "";
        (response.Errors!.Any(e => e.Message.Contains("INVALID_STANDARD_DURATION")) ||
         errorDetails.Contains("INVALID_STANDARD_DURATION") ||
         errorDetails.Contains("12 months")).Should().BeTrue(
             "Standard fiscal year must be exactly 12 months per Årsregnskabsloven §15");
    }

    [Fact]
    public async Task Mutation_CreateFiscalYear_AllowsShorterForFirstYear()
    {
        // Arrange - Per Årsregnskabsloven §15, first fiscal year can be shorter than 12 months
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY First Year Shorter Test");

        // Act - Create 9-month fiscal year WITH isFirstFiscalYear flag
        var response = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) {
                    id
                    name
                }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "First Year 9 Months",
                    startDate = "2024-04-01T00:00:00",
                    endDate = "2025-01-01T00:00:00", // 9 months
                    isFirstFiscalYear = true
                }
            });

        // Assert - Should succeed because first year allows 6-18 months
        response.EnsureNoErrors();
        response.Data!.CreateFiscalYear!.Name.Should().Be("First Year 9 Months");
    }

    [Fact]
    public async Task Mutation_CreateFiscalYear_Allows18MonthsOnlyForReorganization()
    {
        // Arrange - Per Årsregnskabsloven §15, only reorganization allows up to 18 months
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY 18 Months Standard Test");

        // Act - Try to create 18-month fiscal year WITHOUT flags
        var response = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) { id }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "Invalid Standard 18 Months",
                    startDate = "2024-01-01T00:00:00",
                    endDate = "2025-07-01T00:00:00" // 18 months - invalid for standard year
                    // NOT setting isFirstFiscalYear or isReorganization
                }
            });

        // Assert - Should fail because standard years must be 12 months
        response.HasErrors.Should().BeTrue();
        var errorDetails = response.Errors!.First().Extensions?.GetValueOrDefault("details")?.ToString() ?? "";
        (response.Errors!.Any(e => e.Message.Contains("INVALID_STANDARD_DURATION")) ||
         errorDetails.Contains("INVALID_STANDARD_DURATION") ||
         errorDetails.Contains("12 months")).Should().BeTrue(
             "Only reorganization allows 18 months per Årsregnskabsloven §15");
    }

    #endregion

    [Fact]
    public async Task Mutation_ReopenFiscalYear_FailsWhenAlreadyOpen()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY Reopen Already Open Test");

        var createResponse = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) { id status }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "2014",
                    startDate = "2014-01-01T00:00:00",
                    endDate = "2015-01-01T00:00:00"  // Exactly 12 months
                }
            });

        createResponse.EnsureNoErrors();
        var fiscalYearId = createResponse.Data!.CreateFiscalYear!.Id;
        createResponse.Data.CreateFiscalYear.Status.Should().Be("open");

        await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            return await repo.GetByIdAsync(fiscalYearId);
        });

        // Act - Try to reopen an already open fiscal year
        var reopenResponse = await graphqlClient.MutateAsync<ReopenFiscalYearResponse>("""
            mutation ReopenFiscalYear($id: ID!) {
                reopenFiscalYear(id: $id) { id status }
            }
            """,
            new { id = fiscalYearId });

        // Assert
        reopenResponse.HasErrors.Should().BeTrue();
        var errorDetails = reopenResponse.Errors!.First().Extensions?.GetValueOrDefault("details")?.ToString() ?? "";
        (reopenResponse.Errors!.Any(e => e.Message.Contains("ALREADY_OPEN")) ||
         errorDetails.Contains("ALREADY_OPEN") ||
         errorDetails.Contains("already open")).Should().BeTrue();
    }

    [Fact]
    public async Task Query_FiscalYear_ReturnsAuditFieldsAfterReopen()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY Audit Reopen Test");

        var createResponse = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) { id }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "2016",
                    startDate = "2016-01-01T00:00:00",
                    endDate = "2017-01-01T00:00:00"  // Exactly 12 months
                }
            });

        var fiscalYearId = createResponse.Data!.CreateFiscalYear!.Id;

        await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            return await repo.GetByIdAsync(fiscalYearId);
        });

        // Close first
        await graphqlClient.MutateAsync<CloseFiscalYearResponse>(
            $"mutation {{ closeFiscalYear(id: \"{fiscalYearId}\") {{ id }} }}");
        await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            var fy = await repo.GetByIdAsync(fiscalYearId);
            return fy?.Status == "closed" ? fy : null;
        });

        // Reopen
        await graphqlClient.MutateAsync<ReopenFiscalYearResponse>(
            $"mutation {{ reopenFiscalYear(id: \"{fiscalYearId}\") {{ id }} }}");

        // Act - Query to check audit fields
        var queryResponse = await Eventually.GetAsync(async () =>
        {
            var response = await graphqlClient.QueryAsync<FiscalYearQueryResponse>("""
                query FiscalYear($id: ID!) {
                    fiscalYear(id: $id) {
                        id
                        status
                        reopenedDate
                        reopenedBy
                    }
                }
                """,
                new { id = fiscalYearId });

            return response.Data?.FiscalYear?.ReopenedBy != null ? response : null;
        });

        // Assert
        queryResponse.Should().NotBeNull();
        queryResponse!.Data!.FiscalYear!.Status.Should().Be("open");
        queryResponse.Data.FiscalYear.ReopenedBy.Should().NotBeNullOrEmpty();
        queryResponse.Data.FiscalYear.ReopenedDate.Should().NotBeNullOrEmpty();
    }

    [Fact]
    public async Task Query_FiscalYear_ReturnsAuditFieldsAfterLock()
    {
        // Arrange
        var graphqlClient = new GraphQLTestClient(Client);
        var companyId = await CreateCompanyAsync(graphqlClient, "FY Audit Lock Test");

        var createResponse = await graphqlClient.MutateAsync<CreateFiscalYearResponse>("""
            mutation CreateFiscalYear($input: CreateFiscalYearInput!) {
                createFiscalYear(input: $input) { id }
            }
            """,
            new
            {
                input = new
                {
                    companyId,
                    name = "2015",
                    startDate = "2015-01-01T00:00:00",
                    endDate = "2016-01-01T00:00:00"  // Exactly 12 months
                }
            });

        var fiscalYearId = createResponse.Data!.CreateFiscalYear!.Id;

        await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            return await repo.GetByIdAsync(fiscalYearId);
        });

        // Close and lock
        await graphqlClient.MutateAsync<CloseFiscalYearResponse>(
            $"mutation {{ closeFiscalYear(id: \"{fiscalYearId}\") {{ id }} }}");
        await Eventually.GetAsync(async () =>
        {
            var repo = GetService<IFiscalYearRepository>();
            var fy = await repo.GetByIdAsync(fiscalYearId);
            return fy?.Status == "closed" ? fy : null;
        });

        await graphqlClient.MutateAsync<LockFiscalYearResponse>(
            $"mutation {{ lockFiscalYear(id: \"{fiscalYearId}\") {{ id }} }}");

        // Act - Query to check audit fields
        var queryResponse = await Eventually.GetAsync(async () =>
        {
            var response = await graphqlClient.QueryAsync<FiscalYearQueryResponse>("""
                query FiscalYear($id: ID!) {
                    fiscalYear(id: $id) {
                        id
                        status
                        lockedDate
                        lockedBy
                    }
                }
                """,
                new { id = fiscalYearId });

            return response.Data?.FiscalYear?.LockedBy != null ? response : null;
        });

        // Assert
        queryResponse.Should().NotBeNull();
        queryResponse!.Data!.FiscalYear!.Status.Should().Be("locked");
        queryResponse.Data.FiscalYear.LockedBy.Should().NotBeNullOrEmpty();
        queryResponse.Data.FiscalYear.LockedDate.Should().NotBeNullOrEmpty();
    }

    private async Task<string> CreateCompanyAsync(GraphQLTestClient client, string name)
    {
        var response = await client.MutateAsync<CreateCompanyResponse>("""
            mutation CreateCompany($input: CreateCompanyInput!) {
                createCompany(input: $input) { id }
            }
            """,
            new { input = new { name } });

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

        // Set the company ID header for subsequent requests
        client.SetCompanyId(companyId);

        return companyId;
    }

    // Response DTOs
    private class FiscalYearsResponse { public List<FiscalYearDto> FiscalYears { get; set; } = []; }
    private class FiscalYearResponse { public FiscalYearDto? FiscalYear { get; set; } }
    private class FiscalYearQueryResponse { public FiscalYearDto? FiscalYear { get; set; } }
    private class CreateFiscalYearResponse { public FiscalYearDto? CreateFiscalYear { get; set; } }
    private class CloseFiscalYearResponse { public FiscalYearDto? CloseFiscalYear { get; set; } }
    private class ReopenFiscalYearResponse { public FiscalYearDto? ReopenFiscalYear { get; set; } }
    private class LockFiscalYearResponse { public FiscalYearDto? LockFiscalYear { get; set; } }
    private class CreateCompanyResponse { public CompanyDto? CreateCompany { get; set; } }

    private class FiscalYearDto
    {
        public string Id { get; set; } = string.Empty;
        public string CompanyId { get; set; } = string.Empty;
        public string Name { get; set; } = string.Empty;
        public string StartDate { get; set; } = string.Empty;
        public string EndDate { get; set; } = string.Empty;
        public string Status { get; set; } = string.Empty;
        public bool OpeningBalancePosted { get; set; }
        public string? ClosingDate { get; set; }
        public string? ClosedBy { get; set; }
        public string? ReopenedDate { get; set; }
        public string? ReopenedBy { get; set; }
        public string? LockedDate { get; set; }
        public string? LockedBy { get; set; }
    }

    private class CompanyDto { public string Id { get; set; } = string.Empty; }
}
