using Microsoft.Extensions.Logging;
using Moq;
using PARR.Core.Repositories.Interfaces.Schedule;
using PARR.Core.Services.NextRunServices.Subservices;
using PARR.Domain.Entities.Schedule;
using PARR.Domain.Enums;

namespace PARR.DAL.Tests.TransformServices
{
    public class EsppScheduleTransformServiceTests
    {
        private Mock<IEsppSchTypeConfigRepository> esppSchTypeConfigServiceMock;
        //private Mock<INextRunModifierService> nextRunModifierServiceMock;

        private ILogger<EsppScheduleTransformService> logger;
        private EsppScheduleTransformService service;

        public EsppScheduleTransformServiceTests()
        {
            esppSchTypeConfigServiceMock = new Mock<IEsppSchTypeConfigRepository>();
            //nextRunModifierServiceMock = new Mock<INextRunModifierService>();

            var loggerFactory = new LoggerFactory();
            logger = loggerFactory.CreateLogger<EsppScheduleTransformService>();


            service = new EsppScheduleTransformService(
                logger,
                esppSchTypeConfigServiceMock.Object
                //nextRunModifierServiceMock.Object
            );
        }


        [Fact]
        public async Task GetNextDate_Monthly2_ReturnsSameDateForAllReferenceDates()
        {
            // Arrange
            var pastDate = new DateTimeOffset(2024, 10, 10, 10, 0, 0, TimeSpan.Zero); // Прошлое время
            var futureNonMatchingDate = new DateTimeOffset(2025, 12, 08, 10, 0, 0, TimeSpan.Zero); // Будущее, не среда и до ожидаемой даты
            var futureMatchingDate = new DateTimeOffset(2025, 12, 10, 10, 0, 0, TimeSpan.Zero); // Вторая среда и совпадает с ожидаемой датой
            var expectedDate = futureMatchingDate;

            var scheduleDto = new EsppScheduleDto
            {
                TypeSchedule = new EsppSchTypeSchedule
                {
                    Id = (int)EsppSchTypeScheduleEnum.Monthly2,
                    Name = "Ежемесячно-2",
                    Description = "Расписание по дням недели в месяце"
                },
                Values = new List<EsppScheduleValDto>
        {
            new EsppScheduleValDto
            {
                Order = 1,
                Type = new EsppSchType
                {
                    Id = 1,
                    Name = "Порядок",
                    Description = "Порядковый номер дня недели"
                },
                Value = new EsppSchTypeValue
                {
                    Id = Guid.NewGuid(),
                    Value = "Второй",
                    EsppExportValue = "Второй (экспорт)",
                    TypeId = 1,
                    DateCreated = DateTimeOffset.UtcNow,
                    Order = 2
                }
            },
            new EsppScheduleValDto
            {
                Order = 2,
                Type = new EsppSchType
                {
                    Id = 2,
                    Name = "День недели",
                    Description = "День недели (например, понедельник)"
                },
                Value = new EsppSchTypeValue
                {
                    Id = Guid.NewGuid(),
                    Value = "Среда",
                    EsppExportValue = "Среда",
                    TypeId = 2,
                    DateCreated = DateTimeOffset.UtcNow,
                    Order = 3
                }
            }
        }
            };

            esppSchTypeConfigServiceMock
                .Setup(x => x.GetEsppScheduleDtoAsync(It.IsAny<Guid>()))
                .ReturnsAsync(scheduleDto);

            //Act
            var resultFromPast = await service.GetNextDateAsync(Guid.NewGuid(), pastDate);
            var resultFromFutureNonMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureNonMatchingDate);
            var resultFromFutureMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureMatchingDate);

            //Assert
            Assert.Equal(expectedDate, resultFromPast);
            Assert.Equal(expectedDate, resultFromFutureNonMatching);
            Assert.Equal(expectedDate, resultFromFutureMatching);
        }


        [Fact]
        public async Task GetNextDate_Monthly_ReturnsSameDateForAllReferenceDates()
        {
            // Arrange
            var pastDate = new DateTimeOffset(2024, 10, 10, 10, 0, 0, TimeSpan.Zero); // Прошлое время
            var futureNonMatchingDate = new DateTimeOffset(2025, 12, 08, 10, 0, 0, TimeSpan.Zero); // Будущее, не 15-е
            var futureMatchingDate = new DateTimeOffset(2025, 12, 15, 10, 0, 0, TimeSpan.Zero); // 15-е декабря — совпадает с ожидаемой датой
            var expectedDate = futureMatchingDate;

            var scheduleDto = new EsppScheduleDto
            {
                TypeSchedule = new EsppSchTypeSchedule
                {
                    Id = (int)EsppSchTypeScheduleEnum.Monthly,
                    Name = "Ежемесячно",
                    Description = "Расписание по числу месяца"
                },
                Values = new List<EsppScheduleValDto>
        {
            new EsppScheduleValDto
            {
                Order = 1,
                Type = new EsppSchType
                {
                    Id = 1,
                    Name = "День месяца",
                    Description = "Число месяца"
                },
                Value = new EsppSchTypeValue
                {
                    Id = Guid.NewGuid(),
                    Value = "15",
                    EsppExportValue = "15-е число",
                    TypeId = 1,
                    DateCreated = DateTimeOffset.UtcNow,
                    Order = 1
                }
            }
        }
            };

            esppSchTypeConfigServiceMock
                .Setup(x => x.GetEsppScheduleDtoAsync(It.IsAny<Guid>()))
                .ReturnsAsync(scheduleDto);

            var resultFromPast = await service.GetNextDateAsync(Guid.NewGuid(), pastDate);
            var resultFromFutureNonMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureNonMatchingDate);
            var resultFromFutureMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureMatchingDate);

            Assert.Equal(expectedDate, resultFromPast);
            Assert.Equal(expectedDate, resultFromFutureNonMatching);
            Assert.Equal(expectedDate, resultFromFutureMatching);
        }


        [Fact]
        public async Task GetNextDate_Annually_ReturnsSameDateForAllReferenceDates()
        {
            // Arrange
            var pastDate = new DateTimeOffset(2024, 10, 10, 10, 0, 0, TimeSpan.Zero); // Прошлое время
            var futureNonMatchingDate = new DateTimeOffset(2025, 11, 20, 10, 0, 0, TimeSpan.Zero); // Будущее, не январь, не 1-е
            var futureMatchingDate = new DateTimeOffset(2026, 1, 1, 10, 0, 0, TimeSpan.Zero); // 1 января 2026 — совпадает с ожидаемой датой
            var expectedDate = futureMatchingDate; // 1 января 2026

            var scheduleDto = new EsppScheduleDto
            {
                TypeSchedule = new EsppSchTypeSchedule
                {
                    Id = (int)EsppSchTypeScheduleEnum.Annually,
                    Name = "Ежегодно",
                    Description = "Расписание по месяцу и дню года"
                },
                Values = new List<EsppScheduleValDto>
        {
            new EsppScheduleValDto
            {
                Order = 1,
                Type = new EsppSchType
                {
                    Id = 1,
                    Name = "Месяц",
                    Description = "Месяц года"
                },
                Value = new EsppSchTypeValue
                {
                    Id = Guid.NewGuid(),
                    Value = "Январь",
                    EsppExportValue = "Январь",
                    TypeId = 1,
                    DateCreated = DateTimeOffset.UtcNow,
                    Order = 1
                }
            },
            new EsppScheduleValDto
            {
                Order = 2,
                Type = new EsppSchType
                {
                    Id = 2,
                    Name = "День месяца",
                    Description = "Число месяца"
                },
                Value = new EsppSchTypeValue
                {
                    Id = Guid.NewGuid(),
                    Value = "1",
                    EsppExportValue = "1-е число",
                    TypeId = 2,
                    DateCreated = DateTimeOffset.UtcNow,
                    Order = 1
                }
            }
        }
            };

            esppSchTypeConfigServiceMock
                .Setup(x => x.GetEsppScheduleDtoAsync(It.IsAny<Guid>()))
                .ReturnsAsync(scheduleDto);

            // Act
            var resultFromPast = await service.GetNextDateAsync(Guid.NewGuid(), pastDate);
            var resultFromFutureNonMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureNonMatchingDate);
            var resultFromFutureMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureMatchingDate);

            // Assert
            Assert.Equal(expectedDate, resultFromPast);
            Assert.Equal(expectedDate, resultFromFutureNonMatching);
            Assert.Equal(expectedDate, resultFromFutureMatching);
        }


        [Fact]
        public async Task GetNextDate_Annually2_ReturnsSameDateForAllReferenceDates()
        {
            // Arrange
            var pastDate = new DateTimeOffset(2024, 10, 10, 10, 0, 0, TimeSpan.Zero); // Прошлое время
            var futureNonMatchingDate = new DateTimeOffset(2026, 01, 02, 10, 0, 0, TimeSpan.Zero); // Будущее, не понедельник
            var futureMatchingDate = new DateTimeOffset(2026, 1, 5, 10, 0, 0, TimeSpan.Zero); // Первый понедельник января 2026 — совпадает с ожидаемой датой
            var expectedDate = futureMatchingDate;

            var scheduleDto = new EsppScheduleDto
            {
                TypeSchedule = new EsppSchTypeSchedule
                {
                    Id = (int)EsppSchTypeScheduleEnum.Annually2,
                    Name = "Ежегодно-2",
                    Description = "Расписание по порядку дня недели в месяце (например, первый понедельник января)"
                },
                Values = new List<EsppScheduleValDto>
        {
            new EsppScheduleValDto
            {
                Order = 1,
                Type = new EsppSchType
                {
                    Id = 1,
                    Name = "Порядок",
                    Description = "Порядковый номер дня недели (например, первый)"
                },
                Value = new EsppSchTypeValue
                {
                    Id = Guid.NewGuid(),
                    Value = "Первый",
                    EsppExportValue = "Первый",
                    TypeId = 1,
                    DateCreated = DateTimeOffset.UtcNow,
                    Order = 1
                }
            },
            new EsppScheduleValDto
            {
                Order = 2,
                Type = new EsppSchType
                {
                    Id = 2,
                    Name = "День недели",
                    Description = "День недели (например, понедельник)"
                },
                Value = new EsppSchTypeValue
                {
                    Id = Guid.NewGuid(),
                    Value = "Понедельник",
                    EsppExportValue = "Понедельник",
                    TypeId = 2,
                    DateCreated = DateTimeOffset.UtcNow,
                    Order = 2
                }
            },
            new EsppScheduleValDto
            {
                Order = 3,
                Type = new EsppSchType
                {
                    Id = 3,
                    Name = "Месяц",
                    Description = "Месяц года (например, Января)"
                },
                Value = new EsppSchTypeValue
                {
                    Id = Guid.NewGuid(),
                    Value = "Января",
                    EsppExportValue = "Января",
                    TypeId = 3,
                    DateCreated = DateTimeOffset.UtcNow,
                    Order = 3
                }
            }
        }
            };

            esppSchTypeConfigServiceMock
                .Setup(x => x.GetEsppScheduleDtoAsync(It.IsAny<Guid>()))
                .ReturnsAsync(scheduleDto);

            // Act
            var resultFromPast = await service.GetNextDateAsync(Guid.NewGuid(), pastDate);
            var resultFromFutureNonMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureNonMatchingDate);
            var resultFromFutureMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureMatchingDate);

            // Assert
            Assert.Equal(expectedDate, resultFromPast);
            Assert.Equal(expectedDate, resultFromFutureNonMatching);
            Assert.Equal(expectedDate, resultFromFutureMatching);
        }


        [Fact]
        public async Task GetNextDate_Weekly_ReturnsSameDateForAllReferenceDates()
        {
            // Arrange
            var pastDate = new DateTimeOffset(2025, 11, 19, 10, 0, 0, TimeSpan.Zero); // Прошлое время
            var futureNonMatchingDate = new DateTimeOffset(2025, 11, 27, 10, 0, 0, TimeSpan.Zero); // Будущее, четверг — не понедельник
            var futureMatchingDate = new DateTimeOffset(2025, 12, 01, 10, 0, 0, TimeSpan.Zero); // четверг — совпадает с ожидаемой датой
            var expectedDate = futureMatchingDate;

            var scheduleDto = new EsppScheduleDto
            {
                TypeSchedule = new EsppSchTypeSchedule
                {
                    Id = (int)EsppSchTypeScheduleEnum.Weekly,
                    Name = "Еженедельно",
                    Description = "Расписание по дню недели"
                },
                Values = new List<EsppScheduleValDto>
        {
            new EsppScheduleValDto
            {
                Order = 1,
                Type = new EsppSchType
                {
                    Id = 1,
                    Name = "День недели",
                    Description = "День недели"
                },
                Value = new EsppSchTypeValue
                {
                    Id = Guid.NewGuid(),
                    Value = "Понедельник",
                    EsppExportValue = "Понедельник",
                    TypeId = 1,
                    DateCreated = DateTimeOffset.UtcNow,
                    Order = 1
                }
            }
        }
            };

            esppSchTypeConfigServiceMock
                .Setup(x => x.GetEsppScheduleDtoAsync(It.IsAny<Guid>()))
                .ReturnsAsync(scheduleDto);

            // Act
            var resultFromPast = await service.GetNextDateAsync(Guid.NewGuid(), pastDate);
            var resultFromFutureNonMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureNonMatchingDate);
            var resultFromFutureMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureMatchingDate);

            // Assert
            Assert.Equal(expectedDate, resultFromPast);
            Assert.Equal(expectedDate, resultFromFutureNonMatching);
            Assert.Equal(expectedDate, resultFromFutureMatching);
        }


        [Fact]
        public async Task GetNextDate_Regularly_EveryDay()
        {
            // Arrange
            var pastDate = new DateTimeOffset(2025, 11, 20, 20, 0, 0, TimeSpan.Zero); // Прошлое время
            var futureDate = new DateTimeOffset(2025, 11, 30, 10, 0, 0, TimeSpan.Zero); // Будущее время 
            var matchingDate = DateTimeOffset.UtcNow; // Совпадает время 

            var scheduleDto = new EsppScheduleDto
            {
                TypeSchedule = new EsppSchTypeSchedule
                {
                    Id = (int)EsppSchTypeScheduleEnum.Regularly,
                    Name = "Регулярно",
                    Description = "Регулярное расписание"
                },
                Values = new List<EsppScheduleValDto>
        {
            new EsppScheduleValDto
            {
                Order = 1,
                Type = new EsppSchType
                {
                    Id = 1,
                    Name = "Интервал",
                    Description = "Интервал повторения (например, Ежедневно)"
                },
                Value = new EsppSchTypeValue
                {
                    Id = Guid.NewGuid(),
                    Value = "Ежедневно",
                    EsppExportValue = "Ежедневно",
                    TypeId = 1,
                    DateCreated = DateTimeOffset.UtcNow,
                    Order = 1
                }
            }
        }
            };

            esppSchTypeConfigServiceMock
                .Setup(x => x.GetEsppScheduleDtoAsync(It.IsAny<Guid>()))
                .ReturnsAsync(scheduleDto);

            // Act
            var resultFromPast = await service.GetNextDateAsync(Guid.NewGuid(), pastDate);
            var resultFromFuture = await service.GetNextDateAsync(Guid.NewGuid(), futureDate);
            var resultFromMatching = await service.GetNextDateAsync(Guid.NewGuid(), matchingDate);

            // Assert
            Assert.Equal(new DateTimeOffset(
                DateTimeOffset.UtcNow.Year,
                DateTimeOffset.UtcNow.Month,
                DateTimeOffset.UtcNow.Day,
                20, 0, 0, 0, 0, TimeSpan.Zero), resultFromPast);
            Assert.Equal(futureDate, resultFromFuture);
            Assert.Equal(matchingDate.AddDays(1), resultFromMatching);
        }


        [Fact]
        public async Task GetNextDate_Regularly_Every3Year()
        {
            // Arrange
            var pastDate = new DateTimeOffset(2020, 11, 20, 10, 0, 0, TimeSpan.Zero); // Прошлое время
            var futureDate = new DateTimeOffset(2026, 11, 30, 10, 0, 0, TimeSpan.Zero); // Будущее время 
            var matchingDate = DateTimeOffset.UtcNow; // Совпадает время 
            var failureDate = new DateTimeOffset(2027, 01, 01, 23, 59, 0, TimeSpan.Zero); // Дата с ошибкой в работе

            var scheduleDto = new EsppScheduleDto
            {
                TypeSchedule = new EsppSchTypeSchedule
                {
                    Id = (int)EsppSchTypeScheduleEnum.Regularly,
                    Name = "Regularly",
                    Description = "Регулярно"
                },
                Values = new List<EsppScheduleValDto>
        {
            new EsppScheduleValDto
            {
                Order = 1,
                Type = new EsppSchType
                {
                    Id = 1,
                    Name = "Интервал",
                    Description = "Интервал повторения (например, Ежедневно)"
                },
                Value = new EsppSchTypeValue
                {
                    Id = Guid.NewGuid(),
                    Value = "Каждые 3 года",
                    EsppExportValue = "1095 00:00:00",
                    TypeId = 1,
                    DateCreated = DateTimeOffset.UtcNow,
                    Order = 1
                }
            }
        }
            };

            esppSchTypeConfigServiceMock
                .Setup(x => x.GetEsppScheduleDtoAsync(It.IsAny<Guid>()))
                .ReturnsAsync(scheduleDto);

            // Act
            var resultFromPast = await service.GetNextDateAsync(Guid.NewGuid(), pastDate);
            var resultFromFutureMatching = await service.GetNextDateAsync(Guid.NewGuid(), futureDate);
            var resultFromSimpleMatching = await service.GetNextDateAsync(Guid.NewGuid(), matchingDate);
            var resultFromFailure = await service.GetNextDateAsync(Guid.NewGuid(), failureDate);

            // Assert
            Assert.Equal(pastDate.AddHours(26280 * 2), resultFromPast);
            Assert.Equal(futureDate, resultFromFutureMatching);
            Assert.Equal(matchingDate.AddHours(26280), resultFromSimpleMatching);
            Assert.Equal(failureDate, resultFromFailure);
        }


        [Fact]
        public async Task GetNextDate_Weekly_TestUTCToMsk()
        {
            // Arrange
            var beforeMSKTZ = new DateTimeOffset(2025, 11, 25, 3, 0, 0, TimeSpan.Zero); 
            var afterMSKTZ = new DateTimeOffset(2025, 12, 01, 3, 0, 0, TimeSpan.Zero); 

            var scheduleDto = new EsppScheduleDto
            {
                TypeSchedule = new EsppSchTypeSchedule
                {
                    Id = (int)EsppSchTypeScheduleEnum.Weekly,
                    Name = "Еженедельно",
                    Description = "Расписание по дню недели"
                },
                Values = new List<EsppScheduleValDto>
        {
            new EsppScheduleValDto
            {
                Order = 1,
                Type = new EsppSchType
                {
                    Id = 1,
                    Name = "День недели",
                    Description = "День недели"
                },
                Value = new EsppSchTypeValue
                {
                    Id = Guid.NewGuid(),
                    Value = "Понедельник",
                    EsppExportValue = "Понедельник",
                    TypeId = 1,
                    DateCreated = DateTimeOffset.UtcNow,
                    Order = 1
                }
            }
        }
            };

            esppSchTypeConfigServiceMock
                .Setup(x => x.GetEsppScheduleDtoAsync(It.IsAny<Guid>()))
                .ReturnsAsync(scheduleDto);

            // Act
            var resultFrombeforeMSKTZ = await service.GetNextDateAsync(Guid.NewGuid(), beforeMSKTZ);
            var resultFromafterMSKTZ = await service.GetNextDateAsync(Guid.NewGuid(), afterMSKTZ);

            // Assert
            Assert.Equal(new DateTimeOffset(2025, 12, 1, 3, 0, 0, TimeSpan.Zero), resultFrombeforeMSKTZ);
            Assert.Equal(new DateTimeOffset(2025, 12, 1, 3, 0, 0, TimeSpan.Zero), resultFromafterMSKTZ);
        }
    }
}
