using System.Net;
using System.Net.Http.Json;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using ColaFlow.Modules.ProjectManagement.IntegrationTests.Infrastructure;
using ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence;
using ColaFlow.Modules.ProjectManagement.Application.DTOs;
using ColaFlow.Modules.ProjectManagement.Application.Commands.CreateProject;
using ColaFlow.Modules.ProjectManagement.Application.Commands.CreateSprint;
using ColaFlow.Modules.ProjectManagement.Application.Commands.UpdateSprint;
using ColaFlow.Modules.ProjectManagement.Application.Commands.DeleteSprint;
using ColaFlow.Modules.ProjectManagement.Application.Commands.StartSprint;
using ColaFlow.Modules.ProjectManagement.Application.Commands.CompleteSprint;
using ColaFlow.Modules.ProjectManagement.Application.Commands.AddTaskToSprint;
using ColaFlow.Modules.ProjectManagement.Application.Commands.RemoveTaskFromSprint;
using ColaFlow.Modules.ProjectManagement.Application.Queries.GetSprintBurndown;
using Microsoft.EntityFrameworkCore;
using MediatR;

namespace ColaFlow.Modules.ProjectManagement.IntegrationTests;

/// <summary>
/// Comprehensive Integration Tests for Sprint Management (Sprint 2 Story 3 Task 6)
/// Tests all 11 API endpoints, status transitions, business rules, multi-tenant isolation, and burndown calculation
/// </summary>
public class SprintIntegrationTests : IClassFixture<PMWebApplicationFactory>
{
    private readonly PMWebApplicationFactory _factory;
    private readonly HttpClient _client;
    private readonly Guid _tenantId = Guid.NewGuid();
    private readonly Guid _userId = Guid.NewGuid();

    public SprintIntegrationTests(PMWebApplicationFactory factory)
    {
        _factory = factory;
        _client = _factory.CreateClient();

        var token = TestAuthHelper.GenerateJwtToken(_userId, _tenantId, "test-tenant", "user@test.com");
        _client.DefaultRequestHeaders.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
    }

    // ==================== CRUD Tests ====================

    [Fact]
    public async Task CreateSprint_WithValidData_ShouldSucceed()
    {
        // Arrange
        var project = await CreateTestProjectAsync("CRUD Project", "CRUD");
        var startDate = DateTime.UtcNow;
        var endDate = startDate.AddDays(14);

        // Act
        var response = await _client.PostAsJsonAsync("/api/v1/sprints", new
        {
            ProjectId = project.Id,
            Name = "Sprint 1",
            Goal = "Complete user authentication",
            StartDate = startDate,
            EndDate = endDate,
            CreatedBy = _userId
        });

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Created);
        var sprint = await response.Content.ReadFromJsonAsync<SprintDto>();

        sprint.Should().NotBeNull();
        sprint!.Name.Should().Be("Sprint 1");
        sprint.Goal.Should().Be("Complete user authentication");
        sprint.Status.Should().Be("Planned");
        sprint.ProjectId.Should().Be(project.Id);
    }

    [Fact]
    public async Task CreateSprint_WithInvalidDateRange_ShouldFail()
    {
        // Arrange
        var project = await CreateTestProjectAsync("Invalid Project", "INV");

        // Act
        var response = await _client.PostAsJsonAsync("/api/v1/sprints", new
        {
            ProjectId = project.Id,
            Name = "Invalid Sprint",
            StartDate = DateTime.UtcNow,
            EndDate = DateTime.UtcNow.AddDays(-1), // End date before start date
            CreatedBy = _userId
        });

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
    }

    [Fact]
    public async Task UpdateSprint_WithValidData_ShouldSucceed()
    {
        // Arrange
        var sprint = await CreateTestSprintAsync("Original Sprint");

        // Act
        var response = await _client.PutAsync($"/api/v1/sprints/{sprint.Id}", JsonContent.Create(new
        {
            SprintId = sprint.Id,
            Name = "Updated Sprint",
            Goal = "Updated goal",
            StartDate = DateTime.UtcNow,
            EndDate = DateTime.UtcNow.AddDays(21)
        }));

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NoContent);

        // Verify update
        var getResponse = await _client.GetAsync($"/api/v1/sprints/{sprint.Id}");
        var updatedSprint = await getResponse.Content.ReadFromJsonAsync<SprintDto>();
        updatedSprint!.Name.Should().Be("Updated Sprint");
        updatedSprint.Goal.Should().Be("Updated goal");
    }

    [Fact]
    public async Task DeleteSprint_InPlannedStatus_ShouldSucceed()
    {
        // Arrange
        var sprint = await CreateTestSprintAsync("Sprint to Delete");

        // Act
        var response = await _client.DeleteAsync($"/api/v1/sprints/{sprint.Id}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NoContent);

        // Verify deletion
        var getResponse = await _client.GetAsync($"/api/v1/sprints/{sprint.Id}");
        getResponse.StatusCode.Should().Be(HttpStatusCode.NotFound);
    }

    [Fact]
    public async Task DeleteSprint_InActiveStatus_ShouldFail()
    {
        // Arrange
        var sprint = await CreateTestSprintAsync("Active Sprint");
        await _client.PostAsync($"/api/v1/sprints/{sprint.Id}/start", null);

        // Act
        var response = await _client.DeleteAsync($"/api/v1/sprints/{sprint.Id}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
    }

    [Fact]
    public async Task GetSprintById_ShouldReturnSprintWithStatistics()
    {
        // Arrange
        var sprint = await CreateTestSprintAsync("Stats Sprint");

        // Act
        var response = await _client.GetAsync($"/api/v1/sprints/{sprint.Id}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        var result = await response.Content.ReadFromJsonAsync<SprintDto>();

        result.Should().NotBeNull();
        result!.Id.Should().Be(sprint.Id);
        result.Name.Should().Be("Stats Sprint");
        result.Status.Should().Be("Planned");
    }

    // ==================== Status Transition Tests ====================

    [Fact]
    public async Task StartSprint_FromPlanned_ShouldTransitionToActive()
    {
        // Arrange
        var sprint = await CreateTestSprintAsync("Sprint to Start");

        // Act
        var response = await _client.PostAsync($"/api/v1/sprints/{sprint.Id}/start", null);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NoContent);

        // Verify status
        var getResponse = await _client.GetAsync($"/api/v1/sprints/{sprint.Id}");
        var updatedSprint = await getResponse.Content.ReadFromJsonAsync<SprintDto>();
        updatedSprint!.Status.Should().Be("Active");
    }

    [Fact]
    public async Task CompleteSprint_FromActive_ShouldTransitionToCompleted()
    {
        // Arrange
        var sprint = await CreateTestSprintAsync("Sprint to Complete");
        await _client.PostAsync($"/api/v1/sprints/{sprint.Id}/start", null);

        // Act
        var response = await _client.PostAsync($"/api/v1/sprints/{sprint.Id}/complete", null);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NoContent);

        // Verify status
        var getResponse = await _client.GetAsync($"/api/v1/sprints/{sprint.Id}");
        var updatedSprint = await getResponse.Content.ReadFromJsonAsync<SprintDto>();
        updatedSprint!.Status.Should().Be("Completed");
    }

    [Fact]
    public async Task StartSprint_FromActive_ShouldFail()
    {
        // Arrange
        var sprint = await CreateTestSprintAsync("Already Active Sprint");
        await _client.PostAsync($"/api/v1/sprints/{sprint.Id}/start", null);

        // Act
        var response = await _client.PostAsync($"/api/v1/sprints/{sprint.Id}/start", null);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
    }

    [Fact]
    public async Task CompleteSprint_FromPlanned_ShouldFail()
    {
        // Arrange
        var sprint = await CreateTestSprintAsync("Planned Sprint");

        // Act
        var response = await _client.PostAsync($"/api/v1/sprints/{sprint.Id}/complete", null);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
    }

    [Fact]
    public async Task UpdateSprint_InCompletedStatus_ShouldFail()
    {
        // Arrange
        var sprint = await CreateTestSprintAsync("Completed Sprint");
        await _client.PostAsync($"/api/v1/sprints/{sprint.Id}/start", null);
        await _client.PostAsync($"/api/v1/sprints/{sprint.Id}/complete", null);

        // Act
        var response = await _client.PutAsync($"/api/v1/sprints/{sprint.Id}", JsonContent.Create(new
        {
            SprintId = sprint.Id,
            Name = "Try to Update",
            StartDate = DateTime.UtcNow,
            EndDate = DateTime.UtcNow.AddDays(14)
        }));

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
    }

    // ==================== Task Management Tests ====================
    // NOTE: These tests require Task infrastructure to be implemented first.
    // They are skipped for now and will be enabled once Task CRUD APIs are available.

    [Fact(Skip = "Requires Task infrastructure - will be enabled once Task APIs are implemented")]
    public async Task AddTaskToSprint_ShouldSucceed()
    {
        // Arrange
        var sprint = await CreateTestSprintAsync("Task Sprint");
        var taskId = Guid.NewGuid(); // TODO: Create real task once Task API is available

        // Act
        var response = await _client.PostAsync($"/api/v1/sprints/{sprint.Id}/tasks/{taskId}", null);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NoContent);
    }

    [Fact(Skip = "Requires Task infrastructure - will be enabled once Task APIs are implemented")]
    public async Task RemoveTaskFromSprint_ShouldSucceed()
    {
        // Arrange
        var sprint = await CreateTestSprintAsync("Task Removal Sprint");
        var taskId = Guid.NewGuid(); // TODO: Create real task once Task API is available
        await _client.PostAsync($"/api/v1/sprints/{sprint.Id}/tasks/{taskId}", null);

        // Act
        var response = await _client.DeleteAsync($"/api/v1/sprints/{sprint.Id}/tasks/{taskId}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NoContent);
    }

    [Fact(Skip = "Requires Task infrastructure - will be enabled once Task APIs are implemented")]
    public async Task AddTaskToCompletedSprint_ShouldFail()
    {
        // Arrange
        var sprint = await CreateTestSprintAsync("Completed Task Sprint");
        await _client.PostAsync($"/api/v1/sprints/{sprint.Id}/start", null);
        await _client.PostAsync($"/api/v1/sprints/{sprint.Id}/complete", null);
        var taskId = Guid.NewGuid(); // TODO: Create real task once Task API is available

        // Act
        var response = await _client.PostAsync($"/api/v1/sprints/{sprint.Id}/tasks/{taskId}", null);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
    }

    // ==================== Query Tests ====================

    [Fact]
    public async Task GetSprintsByProjectId_ShouldReturnProjectSprints()
    {
        // Arrange
        var project = await CreateTestProjectAsync("Query Project", "QRY");
        var sprint1 = await CreateTestSprintForProjectAsync(project.Id, "Query Sprint 1");
        var sprint2 = await CreateTestSprintForProjectAsync(project.Id, "Query Sprint 2");

        // Act
        var response = await _client.GetAsync($"/api/v1/sprints?projectId={project.Id}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        var sprints = await response.Content.ReadFromJsonAsync<List<SprintDto>>();

        sprints.Should().NotBeNull();
        sprints!.Should().HaveCountGreaterOrEqualTo(2);
        sprints.Should().Contain(s => s.Id == sprint1.Id);
        sprints.Should().Contain(s => s.Id == sprint2.Id);
        sprints.Should().AllSatisfy(s => s.ProjectId.Should().Be(project.Id));
    }

    [Fact]
    public async Task GetActiveSprints_ShouldReturnOnlyActiveSprints()
    {
        // Arrange
        var sprint1 = await CreateTestSprintAsync("Active Test 1");
        var sprint2 = await CreateTestSprintAsync("Active Test 2");
        await _client.PostAsync($"/api/v1/sprints/{sprint1.Id}/start", null);
        // sprint2 stays in Planned status

        // Act
        var response = await _client.GetAsync("/api/v1/sprints/active");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        var activeSprints = await response.Content.ReadFromJsonAsync<List<SprintDto>>();

        activeSprints.Should().NotBeNull();
        activeSprints!.Should().Contain(s => s.Id == sprint1.Id);
        activeSprints.Should().AllSatisfy(s => s.Status.Should().Be("Active"));
    }

    // ==================== Burndown Tests ====================

    [Fact]
    public async Task GetSprintBurndown_ShouldReturnBurndownData()
    {
        // Arrange
        var sprint = await CreateTestSprintAsync("Burndown Sprint");

        // Act
        var response = await _client.GetAsync($"/api/v1/sprints/{sprint.Id}/burndown");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        var burndown = await response.Content.ReadFromJsonAsync<BurndownChartDto>();

        burndown.Should().NotBeNull();
        burndown!.SprintId.Should().Be(sprint.Id);
        burndown.IdealBurndown.Should().NotBeEmpty();
        burndown.ActualBurndown.Should().NotBeEmpty();
    }

    [Fact]
    public async Task GetSprintBurndown_NonExistentSprint_ShouldReturn404()
    {
        // Act
        var response = await _client.GetAsync($"/api/v1/sprints/{Guid.NewGuid()}/burndown");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NotFound);
    }

    // ==================== Multi-Tenant Tests ====================

    [Fact]
    public async Task GetSprintById_DifferentTenant_ShouldReturn404()
    {
        // Arrange: Tenant 1 creates a sprint
        var tenant1Id = Guid.NewGuid();
        var tenant1Token = TestAuthHelper.GenerateJwtToken(_userId, tenant1Id, "tenant1", "user@tenant1.com");
        _client.DefaultRequestHeaders.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tenant1Token);

        var sprint = await CreateTestSprintAsync("Tenant 1 Sprint");

        // Act: Tenant 2 tries to access the sprint
        var tenant2Id = Guid.NewGuid();
        var tenant2Token = TestAuthHelper.GenerateJwtToken(_userId, tenant2Id, "tenant2", "user@tenant2.com");
        _client.DefaultRequestHeaders.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tenant2Token);

        var response = await _client.GetAsync($"/api/v1/sprints/{sprint.Id}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NotFound);
    }

    [Fact]
    public async Task GetSprintsByProjectId_ShouldFilterByTenant()
    {
        // Arrange: Tenant 1 creates a project and sprint
        var tenant1Id = Guid.NewGuid();
        var tenant1Token = TestAuthHelper.GenerateJwtToken(_userId, tenant1Id, "tenant1", "user@tenant1.com");
        _client.DefaultRequestHeaders.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tenant1Token);

        var project1 = await CreateTestProjectAsync("Tenant 1 Project", "T1P");
        var sprint1 = await CreateTestSprintForProjectAsync(project1.Id, "Tenant 1 Sprint");

        // Act: Tenant 2 creates their own project and sprint
        var tenant2Id = Guid.NewGuid();
        var tenant2Token = TestAuthHelper.GenerateJwtToken(_userId, tenant2Id, "tenant2", "user@tenant2.com");
        _client.DefaultRequestHeaders.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tenant2Token);

        var project2 = await CreateTestProjectAsync("Tenant 2 Project", "T2P");
        var sprint2 = await CreateTestSprintForProjectAsync(project2.Id, "Tenant 2 Sprint");

        var response = await _client.GetAsync($"/api/v1/sprints?projectId={project2.Id}");

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        var sprints = await response.Content.ReadFromJsonAsync<List<SprintDto>>();

        sprints.Should().NotBeNull();
        sprints!.Should().Contain(s => s.Id == sprint2.Id);
        sprints.Should().NotContain(s => s.Id == sprint1.Id);
    }

    [Fact]
    public async Task GetActiveSprints_ShouldFilterByTenant()
    {
        // Arrange: Tenant 1 creates and starts a sprint
        var tenant1Id = Guid.NewGuid();
        var tenant1Token = TestAuthHelper.GenerateJwtToken(_userId, tenant1Id, "tenant1", "user@tenant1.com");
        _client.DefaultRequestHeaders.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tenant1Token);

        var sprint1 = await CreateTestSprintAsync("Tenant 1 Active Sprint");
        await _client.PostAsync($"/api/v1/sprints/{sprint1.Id}/start", null);

        // Act: Tenant 2 gets active sprints
        var tenant2Id = Guid.NewGuid();
        var tenant2Token = TestAuthHelper.GenerateJwtToken(_userId, tenant2Id, "tenant2", "user@tenant2.com");
        _client.DefaultRequestHeaders.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tenant2Token);

        var response = await _client.GetAsync("/api/v1/sprints/active");

        // Assert
        var activeSprints = await response.Content.ReadFromJsonAsync<List<SprintDto>>();
        activeSprints.Should().NotBeNull();
        activeSprints!.Should().NotContain(s => s.Id == sprint1.Id, "Tenant 2 should not see Tenant 1's sprints");
    }

    // ==================== Business Rule Validation Tests ====================

    [Fact]
    public async Task CreateSprint_WithEmptyName_ShouldFail()
    {
        // Arrange
        var project = await CreateTestProjectAsync("Validation Project", "VAL");
        var command = new CreateSprintCommand
        {
            ProjectId = project.Id,
            Name = "", // Empty name
            StartDate = DateTime.UtcNow,
            EndDate = DateTime.UtcNow.AddDays(14),
            CreatedBy = _userId
        };

        // Act
        var response = await _client.PostAsJsonAsync("/api/v1/sprints", command);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
    }

    [Fact]
    public async Task CreateSprint_WithNonExistentProject_ShouldFail()
    {
        // Act
        var response = await _client.PostAsJsonAsync("/api/v1/sprints", new
        {
            ProjectId = Guid.NewGuid(), // Non-existent project
            Name = "Orphan Sprint",
            StartDate = DateTime.UtcNow,
            EndDate = DateTime.UtcNow.AddDays(14),
            CreatedBy = _userId
        });

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NotFound);
    }

    // ==================== Helper Methods ====================

    private async Task<ProjectDto> CreateTestProjectAsync(string name, string key)
    {
        var response = await _client.PostAsJsonAsync("/api/v1/projects", new
        {
            Name = name,
            Key = key,
            Description = "Test project for Sprint integration tests"
        });
        response.EnsureSuccessStatusCode();
        var project = await response.Content.ReadFromJsonAsync<ProjectDto>();
        return project!;
    }

    private async Task<SprintDto> CreateTestSprintAsync(string name)
    {
        var randomSuffix = Random.Shared.Next(100, 999);
        var project = await CreateTestProjectAsync($"Project for {name}", $"P{randomSuffix}");
        return await CreateTestSprintForProjectAsync(project.Id, name);
    }

    private async Task<SprintDto> CreateTestSprintForProjectAsync(Guid projectId, string name)
    {
        var response = await _client.PostAsJsonAsync("/api/v1/sprints", new
        {
            ProjectId = projectId,
            Name = name,
            Goal = "Test goal",
            StartDate = DateTime.UtcNow,
            EndDate = DateTime.UtcNow.AddDays(14),
            CreatedBy = _userId
        });
        response.EnsureSuccessStatusCode();
        var sprint = await response.Content.ReadFromJsonAsync<SprintDto>();
        return sprint!;
    }
}
