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 AnnouncementsControllerTests : IDisposable
{
    private readonly RacePlannerDbContext _context;
    private readonly AnnouncementsController _controller;

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

        _context = new RacePlannerDbContext(options);
        _controller = new AnnouncementsController(_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 Announcement - Positive Tests

    [Fact]
    public async Task CreateAnnouncement_WithValidData_CreatesAnnouncement()
    {
        // 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();

        SetUserContext(organizer.Id, "Organizer");

        var request = new CreateAnnouncementRequest
        {
            EventId = eventEntity.Id,
            Title = "Important Update",
            Content = "Event details have been updated."
        };

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

        // Assert
        var createdResult = result.Result.Should().BeOfType<CreatedAtActionResult>().Subject;
        var response = createdResult.Value.Should().BeOfType<AnnouncementDto>().Subject;
        response.Title.Should().Be(request.Title);
        response.Content.Should().Be(request.Content);
        response.IsPublished.Should().BeTrue();
    }

    #endregion

    #region Create Announcement - Negative Tests

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

        SetUserContext(organizer.Id, "Organizer");

        var request = new CreateAnnouncementRequest
        {
            EventId = Guid.NewGuid(),
            Title = "Test",
            Content = "Content"
        };

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

        // Assert
        var notFoundResult = result.Result.Should().BeOfType<NotFoundObjectResult>().Subject;
        notFoundResult.Value.Should().BeEquivalentTo(new { error = "Event not found" });
    }

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

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

        SetUserContext(otherOrganizer.Id, "Organizer");

        var request = new CreateAnnouncementRequest
        {
            EventId = eventEntity.Id,
            Title = "Test",
            Content = "Content"
        };

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

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

    #endregion

    #region Get Announcement - Positive Tests

    [Fact]
    public async Task GetAnnouncement_ReturnsPublishedAnnouncement()
    {
        // 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);

        var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Title", "Content", organizer.Id);
        announcement.IsPublished = true;
        _context.Announcements.Add(announcement);
        await _context.SaveChangesAsync();

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

        // Act
        var result = await _controller.GetAnnouncement(announcement.Id);

        // Assert
        var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
        var response = okResult.Value.Should().BeOfType<AnnouncementDto>().Subject;
        response.Title.Should().Be("Title");
    }

    [Fact]
    public async Task GetAnnouncement_OrganizerCanViewUnpublishedAnnouncement()
    {
        // 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);

        var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Draft Title", "Content", organizer.Id);
        announcement.IsPublished = false;
        _context.Announcements.Add(announcement);
        await _context.SaveChangesAsync();

        SetUserContext(organizer.Id, "Organizer");

        // Act
        var result = await _controller.GetAnnouncement(announcement.Id);

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

    #endregion

    #region Get Announcement - Negative Tests

    [Fact]
    public async Task GetAnnouncement_UnpublishedNotVisibleToPublic()
    {
        // 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);

        var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Draft", "Content", organizer.Id);
        announcement.IsPublished = false;
        _context.Announcements.Add(announcement);
        await _context.SaveChangesAsync();

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

        // Act
        var result = await _controller.GetAnnouncement(announcement.Id);

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

    #endregion

    #region Get Event Announcements

    [Fact]
    public async Task GetEventAnnouncements_ReturnsPublishedAnnouncements()
    {
        // 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);

        var published = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Published", "Content", organizer.Id);
        published.IsPublished = true;
        var unpublished = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Draft", "Content", organizer.Id);
        unpublished.IsPublished = false;
        _context.Announcements.AddRange(published, unpublished);
        await _context.SaveChangesAsync();

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

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

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

    [Fact]
    public async Task GetEventAnnouncements_OrganizerSeesAll()
    {
        // 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);

        var published = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Published", "Content", organizer.Id);
        published.IsPublished = true;
        var unpublished = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Draft", "Content", organizer.Id);
        unpublished.IsPublished = false;
        _context.Announcements.AddRange(published, unpublished);
        await _context.SaveChangesAsync();

        SetUserContext(organizer.Id, "Organizer");

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

        // Assert
        var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
        var announcements = okResult.Value.Should().BeAssignableTo<IEnumerable<AnnouncementDto>>().Subject;
        announcements.Should().HaveCount(2);
    }

    #endregion

    #region Update Announcement - Positive Tests

    [Fact]
    public async Task UpdateAnnouncement_WithValidData_UpdatesAnnouncement()
    {
        // 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);

        var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Original", "Content", organizer.Id);
        _context.Announcements.Add(announcement);
        await _context.SaveChangesAsync();

        SetUserContext(organizer.Id, "Organizer");

        var request = new UpdateAnnouncementRequest
        {
            Title = "Updated Title",
            Content = "Updated Content"
        };

        // Act
        var result = await _controller.UpdateAnnouncement(announcement.Id, request);

        // Assert
        var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
        var response = okResult.Value.Should().BeOfType<AnnouncementDto>().Subject;
        response.Title.Should().Be("Updated Title");
        response.Content.Should().Be("Updated Content");
    }

    [Fact]
    public async Task UpdateAnnouncement_PublishUnpublishedAnnouncement()
    {
        // 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);

        var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Draft", "Content", organizer.Id);
        announcement.IsPublished = false;
        _context.Announcements.Add(announcement);
        await _context.SaveChangesAsync();

        SetUserContext(organizer.Id, "Organizer");

        var request = new UpdateAnnouncementRequest { IsPublished = true };

        // Act
        var result = await _controller.UpdateAnnouncement(announcement.Id, request);

        // Assert
        var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
        var response = okResult.Value.Should().BeOfType<AnnouncementDto>().Subject;
        response.IsPublished.Should().BeTrue();
    }

    #endregion

    #region Update Announcement - Negative Tests

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

        SetUserContext(organizer.Id, "Organizer");

        var request = new UpdateAnnouncementRequest { Title = "Updated" };

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

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

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

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

        var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Title", "Content", organizer.Id);
        _context.Announcements.Add(announcement);
        await _context.SaveChangesAsync();

        SetUserContext(otherOrganizer.Id, "Organizer");

        var request = new UpdateAnnouncementRequest { Title = "Hacked" };

        // Act
        var result = await _controller.UpdateAnnouncement(announcement.Id, request);

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

    #endregion

    #region Delete Announcement - Positive Tests

    [Fact]
    public async Task DeleteAnnouncement_OrganizerCanDeleteOwnAnnouncement()
    {
        // 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);

        var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "To Delete", "Content", organizer.Id);
        _context.Announcements.Add(announcement);
        await _context.SaveChangesAsync();

        SetUserContext(organizer.Id, "Organizer");

        // Act
        var result = await _controller.DeleteAnnouncement(announcement.Id);

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

        var deleted = await _context.Announcements.FindAsync(announcement.Id);
        deleted.Should().BeNull();
    }

    #endregion

    #region Delete Announcement - Negative Tests

    [Fact]
    public async Task DeleteAnnouncement_NonExistent_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.DeleteAnnouncement(Guid.NewGuid());

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

    #endregion

    #region Get My Announcements

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

        var event1 = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
        var event2 = TestDataFactory.CreateEvent(name: "Event 2", organizerId: organizer.Id, status: EventStatus.Published);
        _context.Events.AddRange(event1, event2);

        var registration = TestDataFactory.CreateRegistration(event1.Id, participant.Id, RegistrationStatus.Confirmed);
        _context.Registrations.Add(registration);

        var announcement1 = TestDataFactory.CreateAnnouncement(event1.Id, "Announcement 1", "Content", organizer.Id);
        announcement1.IsPublished = true;
        var announcement2 = TestDataFactory.CreateAnnouncement(event2.Id, "Announcement 2", "Content", organizer.Id);
        announcement2.IsPublished = true;
        _context.Announcements.AddRange(announcement1, announcement2);
        await _context.SaveChangesAsync();

        SetUserContext(participant.Id, "Participant");

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

        // Assert
        var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
        var announcements = okResult.Value.Should().BeAssignableTo<IEnumerable<AnnouncementDto>>().Subject;
        announcements.Should().HaveCount(1);
        announcements.First().Title.Should().Be("Announcement 1");
    }

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

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

        var registration = TestDataFactory.CreateRegistration(eventEntity.Id, participant.Id, RegistrationStatus.Confirmed);
        _context.Registrations.Add(registration);

        var published = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Published", "Content", organizer.Id);
        published.IsPublished = true;
        var unpublished = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Unpublished", "Content", organizer.Id);
        unpublished.IsPublished = false;
        _context.Announcements.AddRange(published, unpublished);
        await _context.SaveChangesAsync();

        SetUserContext(participant.Id, "Participant");

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

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

    #endregion
}
