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

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

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

    [Fact]
    public async Task RecordPayment_WithValidData_RecordsPayment()
    {
        // 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 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);
        await _context.SaveChangesAsync();

        SetUserContext(organizer.Id, "Organizer");

        var request = new CreatePaymentRequest
        {
            RegistrationId = registration.Id,
            Amount = 50.00m,
            Method = PaymentMethod.Cash,
            Notes = "Cash payment received"
        };

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

        // Assert
        var createdResult = result.Result.Should().BeOfType<CreatedAtActionResult>().Subject;
        var response = createdResult.Value.Should().BeOfType<PaymentDto>().Subject;
        response.Amount.Should().Be(50.00m);
        response.Method.Should().Be("Cash");
    }

    [Fact]
    public async Task RecordPayment_OnlinePayment_WithTransactionId()
    {
        // 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);
        await _context.SaveChangesAsync();

        SetUserContext(organizer.Id, "Organizer");

        var request = new CreatePaymentRequest
        {
            RegistrationId = registration.Id,
            Amount = 75.00m,
            Method = PaymentMethod.Online,
            TransactionId = "txn_12345",
            Notes = "Online payment"
        };

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

        // Assert
        var createdResult = result.Result.Should().BeOfType<CreatedAtActionResult>().Subject;
        var response = createdResult.Value.Should().BeOfType<PaymentDto>().Subject;
        response.Method.Should().Be("Online");
        response.TransactionId.Should().Be("txn_12345");
    }

    #endregion

    #region Record Payment - Negative Tests

    [Fact]
    public async Task RecordPayment_ForCancelledRegistration_ReturnsBadRequest()
    {
        // Arrange
        var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
        var participant = TestDataFactory.CreateUser(email: "participant3@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.Cancelled);
        _context.Registrations.Add(registration);
        await _context.SaveChangesAsync();

        SetUserContext(organizer.Id, "Organizer");

        var request = new CreatePaymentRequest
        {
            RegistrationId = registration.Id,
            Amount = 50.00m,
            Method = PaymentMethod.Cash
        };

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

        // Assert
        var badRequestResult = result.Result.Should().BeOfType<BadRequestObjectResult>().Subject;
        badRequestResult.Value.Should().BeEquivalentTo(new { error = "Cannot record payment for cancelled registration" });
    }

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

        SetUserContext(organizer.Id, "Organizer");

        var request = new CreatePaymentRequest
        {
            RegistrationId = Guid.NewGuid(),
            Amount = 50.00m,
            Method = PaymentMethod.Cash
        };

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

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

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

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

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

        SetUserContext(otherOrganizer.Id, "Organizer");

        var request = new CreatePaymentRequest
        {
            RegistrationId = registration.Id,
            Amount = 50.00m,
            Method = PaymentMethod.Cash
        };

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

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

    #endregion

    #region Get Payment Status - Positive Tests

    [Fact]
    public async Task GetPaymentStatus_UnpaidRegistration_ReturnsUnpaid()
    {
        // Arrange
        var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
        var participant = TestDataFactory.CreateUser(email: "participant5@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);
        _context.Registrations.Add(registration);
        await _context.SaveChangesAsync();

        SetUserContext(participant.Id, "Participant");

        // Act
        var result = await _controller.GetPaymentStatus(registration.Id);

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

    [Fact]
    public async Task GetPaymentStatus_PaidRegistration_ReturnsPaid()
    {
        // Arrange
        var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
        var participant = TestDataFactory.CreateUser(email: "participant6@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);
        _context.Registrations.Add(registration);
        await _context.SaveChangesAsync();

        // Record a payment
        var payment = TestDataFactory.CreatePayment(registration.Id, 50.00m, PaymentMethod.Cash);
        _context.Payments.Add(payment);
        await _context.SaveChangesAsync();

        SetUserContext(participant.Id, "Participant");

        // Act
        var result = await _controller.GetPaymentStatus(registration.Id);

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

    #endregion

    #region Get Payment Status - Negative Tests

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

        SetUserContext(participant.Id, "Participant");

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

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

    #endregion

    #region Get Payment Report - Positive Tests

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

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

        var registration1 = TestDataFactory.CreateRegistration(eventEntity.Id, participant1.Id, RegistrationStatus.Confirmed);
        var registration2 = TestDataFactory.CreateRegistration(eventEntity.Id, participant2.Id, RegistrationStatus.Confirmed);
        _context.Registrations.AddRange(registration1, registration2);
        await _context.SaveChangesAsync();

        // Add payment for first registration
        var payment = TestDataFactory.CreatePayment(registration1.Id, 50.00m, PaymentMethod.Cash);
        _context.Payments.Add(payment);
        await _context.SaveChangesAsync();

        SetUserContext(organizer.Id, "Organizer");

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

        // Assert
        var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
        var response = okResult.Value.Should().BeOfType<PaymentReportDto>().Subject;
        response.TotalCollected.Should().Be(50.00m);
        response.TotalRegistrations.Should().Be(2);
        response.PaidRegistrations.Should().Be(1);
        response.UnpaidRegistrations.Should().Be(1);
    }

    #endregion

    #region Get Payment Report - Negative Tests

    [Fact]
    public async Task GetPaymentReport_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");

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

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

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

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

    #endregion
}
