using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Moq;
using FluentAssertions;
using RacePlannerApi.Controllers;
using RacePlannerApi.Data;
using RacePlannerApi.DTOs;
using RacePlannerApi.Models;
using backend.Tests.Utilities;

namespace backend.Tests.Controllers;

public class EventsControllerTests : IDisposable
{
    private readonly RacePlannerDbContext _context;
    private readonly EventsController _controller;

    public EventsControllerTests()
    {
        var options = new DbContextOptionsBuilder<RacePlannerDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        _context = new RacePlannerDbContext(options);
        _controller = new EventsController(_context);
    }

    private void SetUserContext(Guid userId, string role = "Participant")
    {
        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
            new Claim(ClaimTypes.Role, role)
        };
        var identity = new ClaimsIdentity(claims, "TestAuthType");
        var principal = new ClaimsPrincipal(identity);
        _controller.ControllerContext = new ControllerContext
        {
            HttpContext = new DefaultHttpContext { User = principal }
        };
    }

    public void Dispose()
    {
        _context.Dispose();
    }

    #region Create Event - Positive Tests

    [Fact]
    public async Task CreateEvent_WithValidData_CreatesEvent()
    {
        // Arrange
        var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
        _context.Users.Add(organizer);
        await _context.SaveChangesAsync();
        SetUserContext(organizer.Id, "Organizer");

        var request = new CreateEventRequest
        {
            Name = "Test Marathon",
            Description = "A test marathon event",
            EventDate = DateTime.UtcNow.AddDays(30),
            Location = "Test City",
            Category = "Running",
            Tags = new List<string> { "marathon", "running" },
            MaxParticipants = 100
        };

        // Act
        var result = await _controller.CreateEvent(request);

        // Assert
        var createdResult = result.Result.Should().BeOfType<CreatedAtActionResult>().Subject;
        var response = createdResult.Value.Should().BeOfType<EventDto>().Subject;
        response.Name.Should().Be(request.Name);
        response.Status.Should().Be("Draft");
        response.Organizer.Id.Should().Be(organizer.Id);
    }

    [Fact]
    public async Task CreateEvent_SetsStatusToDraft()
    {
        // Arrange
        var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
        _context.Users.Add(organizer);
        await _context.SaveChangesAsync();
        SetUserContext(organizer.Id, "Organizer");

        var request = new CreateEventRequest
        {
            Name = "Test Event",
            EventDate = DateTime.UtcNow.AddDays(10),
            Location = "Test Location",
            MaxParticipants = 50
        };

        // Act
        var result = await _controller.CreateEvent(request);

        // Assert
        var createdResult = result.Result.Should().BeOfType<CreatedAtActionResult>().Subject;
        var response = createdResult.Value.Should().BeOfType<EventDto>().Subject;
        response.Status.Should().Be("Draft");
    }

    #endregion

    #region Create Event - Negative Tests

    [Fact(Skip = "Authorization attributes require integration tests with full ASP.NET Core pipeline")]
    public async Task CreateEvent_WithoutOrganizerRole_ReturnsForbidden()
    {
        // Arrange
        var participant = TestDataFactory.CreateUser(role: UserRole.Participant);
        _context.Users.Add(participant);
        await _context.SaveChangesAsync();
        SetUserContext(participant.Id, "Participant");

        var request = new CreateEventRequest
        {
            Name = "Test Event",
            EventDate = DateTime.UtcNow.AddDays(10),
            Location = "Test Location"
        };

        // Act
        var result = await _controller.CreateEvent(request);

        // Assert
        result.Result.Should().BeOfType<ForbidResult>();
    }

    [Fact]
    public async Task CreateEvent_WithUnauthenticatedUser_ReturnsUnauthorized()
    {
        // Arrange
        _controller.ControllerContext = new ControllerContext
        {
            HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() }
        };

        var request = new CreateEventRequest
        {
            Name = "Test Event",
            EventDate = DateTime.UtcNow.AddDays(10),
            Location = "Test Location"
        };

        // Act
        var result = await _controller.CreateEvent(request);

        // Assert
        result.Result.Should().BeOfType<UnauthorizedResult>();
    }

    #endregion

    #region Get Events - Positive Tests

    [Fact]
    public async Task GetEvents_ReturnsPublishedEventsForAnonymous()
    {
        // Arrange
        var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
        _context.Users.Add(organizer);

        var publishedEvent = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
        var draftEvent = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Draft);
        _context.Events.AddRange(publishedEvent, draftEvent);
        await _context.SaveChangesAsync();

        _controller.ControllerContext = new ControllerContext
        {
            HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() }
        };

        // Act
        var result = await _controller.GetEvents();

        // Assert
        var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
        var events = okResult.Value.Should().BeAssignableTo<IEnumerable<EventDto>>().Subject;
        events.Should().HaveCount(1);
        events.First().Status.Should().Be("Published");
    }

    [Fact]
    public async Task GetEvents_WithCategoryFilter_ReturnsFilteredEvents()
    {
        // Arrange
        var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
        _context.Users.Add(organizer);

        var runningEvent = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
        runningEvent.Category = "Running";
        var cyclingEvent = TestDataFactory.CreateEvent(name: "Cycling Event", organizerId: organizer.Id, status: EventStatus.Published);
        cyclingEvent.Category = "Cycling";

        _context.Events.AddRange(runningEvent, cyclingEvent);
        await _context.SaveChangesAsync();

        // Set anonymous context
        _controller.ControllerContext = new ControllerContext
        {
            HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() }
        };

        var filter = new EventFilterRequest { Category = "Running" };

        // Act
        var result = await _controller.GetEvents(filter);

        // Assert
        var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
        var events = okResult.Value.Should().BeAssignableTo<IEnumerable<EventDto>>().Subject;
        events.Should().HaveCount(1);
        events.First().Category.Should().Be("Running");
    }

    [Fact]
    public async Task GetEvents_WithDateRangeFilter_ReturnsFilteredEvents()
    {
        // Arrange
        var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
        _context.Users.Add(organizer);

        var futureEvent = TestDataFactory.CreateEvent(
            name: "Future Event",
            eventDate: DateTime.UtcNow.AddDays(60),
            organizerId: organizer.Id,
            status: EventStatus.Published);
        var pastEvent = TestDataFactory.CreateEvent(
            name: "Past Event",
            eventDate: DateTime.UtcNow.AddDays(-10),
            organizerId: organizer.Id,
            status: EventStatus.Published);

        _context.Events.AddRange(futureEvent, pastEvent);
        await _context.SaveChangesAsync();

        // Set anonymous context
        _controller.ControllerContext = new ControllerContext
        {
            HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() }
        };

        var filter = new EventFilterRequest
        {
            FromDate = DateTime.UtcNow.AddDays(1),
            ToDate = DateTime.UtcNow.AddDays(100)
        };

        // Act
        var result = await _controller.GetEvents(filter);

        // Assert
        var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
        var events = okResult.Value.Should().BeAssignableTo<IEnumerable<EventDto>>().Subject;
        events.Should().HaveCount(1);
        events.First().Name.Should().Be("Future Event");
    }

    #endregion

    #region Get Single Event - Positive Tests

    [Fact]
    public async Task GetEvent_ReturnsPublishedEvent()
    {
        // Arrange
        var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
        _context.Users.Add(organizer);

        var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
        _context.Events.Add(eventEntity);
        await _context.SaveChangesAsync();

        _controller.ControllerContext = new ControllerContext
        {
            HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() }
        };

        // Act
        var result = await _controller.GetEvent(eventEntity.Id);

        // Assert
        var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
        var response = okResult.Value.Should().BeOfType<EventDto>().Subject;
        response.Id.Should().Be(eventEntity.Id);
        response.Name.Should().Be(eventEntity.Name);
    }

    [Fact]
    public async Task GetEvent_OrganizerCanViewOwnDraftEvent()
    {
        // Arrange
        var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
        _context.Users.Add(organizer);

        var draftEvent = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Draft);
        _context.Events.Add(draftEvent);
        await _context.SaveChangesAsync();

        SetUserContext(organizer.Id, "Organizer");

        // Act
        var result = await _controller.GetEvent(draftEvent.Id);

        // Assert
        result.Result.Should().BeOfType<OkObjectResult>();
    }

    #endregion

    #region Get Single Event - Negative Tests

    [Fact]
    public async Task GetEvent_DraftEventNotVisibleToPublic()
    {
        // Arrange
        var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
        _context.Users.Add(organizer);

        var draftEvent = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Draft);
        _context.Events.Add(draftEvent);
        await _context.SaveChangesAsync();

        _controller.ControllerContext = new ControllerContext
        {
            HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() }
        };

        // Act
        var result = await _controller.GetEvent(draftEvent.Id);

        // Assert
        result.Result.Should().BeOfType<NotFoundResult>();
    }

    [Fact]
    public async Task GetEvent_NonExistentEvent_ReturnsNotFound()
    {
        // Arrange
        var nonExistentId = Guid.NewGuid();

        _controller.ControllerContext = new ControllerContext
        {
            HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() }
        };

        // Act
        var result = await _controller.GetEvent(nonExistentId);

        // Assert
        result.Result.Should().BeOfType<NotFoundResult>();
    }

    #endregion

    #region Update Event - Positive Tests

    [Fact]
    public async Task UpdateEvent_WithValidData_UpdatesEvent()
    {
        // Arrange
        var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
        _context.Users.Add(organizer);

        var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Draft);
        _context.Events.Add(eventEntity);
        await _context.SaveChangesAsync();

        SetUserContext(organizer.Id, "Organizer");

        var request = new UpdateEventRequest
        {
            Name = "Updated Event Name",
            Description = "Updated description",
            Status = EventStatus.Published
        };

        // Act
        var result = await _controller.UpdateEvent(eventEntity.Id, request);

        // Assert
        var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
        var response = okResult.Value.Should().BeOfType<EventDto>().Subject;
        response.Name.Should().Be("Updated Event Name");
        response.Status.Should().Be("Published");
    }

    #endregion

    #region Update Event - Negative Tests

    [Fact]
    public async Task UpdateEvent_NonOrganizer_ReturnsForbidden()
    {
        // Arrange
        var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
        var otherUser = TestDataFactory.CreateUser(email: "other@example.com", role: UserRole.Organizer);
        _context.Users.AddRange(organizer, otherUser);

        var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id);
        _context.Events.Add(eventEntity);
        await _context.SaveChangesAsync();

        SetUserContext(otherUser.Id, "Organizer");

        var request = new UpdateEventRequest { Name = "Hacked Name" };

        // Act
        var result = await _controller.UpdateEvent(eventEntity.Id, request);

        // Assert
        result.Result.Should().BeOfType<ForbidResult>();
    }

    [Fact]
    public async Task UpdateEvent_NonExistentEvent_ReturnsNotFound()
    {
        // Arrange
        var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
        _context.Users.Add(organizer);
        await _context.SaveChangesAsync();

        SetUserContext(organizer.Id, "Organizer");

        var request = new UpdateEventRequest { Name = "Updated Name" };

        // Act
        var result = await _controller.UpdateEvent(Guid.NewGuid(), request);

        // Assert
        result.Result.Should().BeOfType<NotFoundResult>();
    }

    #endregion

    #region Delete Event - Positive Tests

    [Fact]
    public async Task DeleteEvent_OrganizerCanDeleteOwnEvent()
    {
        // Arrange
        var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
        _context.Users.Add(organizer);

        var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id);
        _context.Events.Add(eventEntity);
        await _context.SaveChangesAsync();

        SetUserContext(organizer.Id, "Organizer");

        // Act
        var result = await _controller.DeleteEvent(eventEntity.Id);

        // Assert
        result.Should().BeOfType<NoContentResult>();

        var deletedEvent = await _context.Events.FindAsync(eventEntity.Id);
        deletedEvent.Should().BeNull();
    }

    #endregion

    #region Delete Event - Negative Tests

    [Fact]
    public async Task DeleteEvent_NonOrganizer_ReturnsForbidden()
    {
        // Arrange
        var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
        var otherUser = TestDataFactory.CreateUser(email: "other@example.com", role: UserRole.Organizer);
        _context.Users.AddRange(organizer, otherUser);

        var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id);
        _context.Events.Add(eventEntity);
        await _context.SaveChangesAsync();

        SetUserContext(otherUser.Id, "Organizer");

        // Act
        var result = await _controller.DeleteEvent(eventEntity.Id);

        // Assert
        result.Should().BeOfType<ForbidResult>();
    }

    [Fact]
    public async Task DeleteEvent_NonExistentEvent_ReturnsNotFound()
    {
        // Arrange
        var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
        _context.Users.Add(organizer);
        await _context.SaveChangesAsync();

        SetUserContext(organizer.Id, "Organizer");

        // Act
        var result = await _controller.DeleteEvent(Guid.NewGuid());

        // Assert
        result.Should().BeOfType<NotFoundResult>();
    }

    #endregion
}
