using AsbCloudApp.Data.WellOperation;
using AsbCloudApp.Requests;
using AsbCloudDb.Model;
using AsbCloudInfrastructure;
using AsbCloudWebApi.IntegrationTests.Clients;
using AsbCloudWebApi.IntegrationTests.Data;
using ClosedXML.Excel;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Refit;
using System.Net;
using System.Reflection;
using Xunit;

namespace AsbCloudWebApi.IntegrationTests.Controllers.WellOperations;

public class WellOperationControllerTest : BaseIntegrationTest
{
    private IWellOperationClient client;

    public WellOperationControllerTest(WebAppFactoryFixture factory)
        : base(factory)
    {
        client = factory.GetAuthorizedHttpClient<IWellOperationClient>(string.Empty);

        dbContext.CleanupDbSet<WellOperation>();
    }

    /// <summary>
    /// Óñïåøíîå äîáàâëåíèå îïåðàöèé (áåç ïðåäâàðèòåëüíîé î÷èñòêè äàííûõ)
    /// </summary>
    /// <returns></returns>
    [Fact]
    public async Task InsertRange_returns_success()
    {
        //arrange
        var well = await dbContext.Wells.FirstAsync();
        var entity = CreateWellOperation(well.Id);
        var dtos = new[] { entity.Adapt<WellOperationDto>() };

        //act
        var response = await client.InsertRangeAsync(well.Id, false, dtos);

        //assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }

    /// <summary>
    /// Óñïåøíîå äîáàâëåíèå îïåðàöèé (ñ ïðåäâàðèòåëüíîé î÷èñòêîé äàííûõ)
    /// </summary>
    /// <returns></returns>
    [Fact]
    public async Task InsertRangeWithDeleteBefore_returns_success()
    {
        //arrange
        var well = await dbContext.Wells.FirstAsync();
        var entity = CreateWellOperation(well.Id);
        var dtos = new[] { entity.Adapt<WellOperationDto>() };

        //act
        var response = await client.InsertRangeAsync(well.Id, true, dtos);

        //assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }

    /// <summary>
    /// Óñïåøíîå îáíîâëåíèå îïåðàöèé
    /// </summary>
    /// <returns></returns>
    [Fact]
    public async Task UpdateRangeAsync_returns_success()
    {
        //arrange
        var well = await dbContext.Wells.FirstAsync();
        var entity = CreateWellOperation(well.Id);
        dbContext.WellOperations.Add(entity);
        await dbContext.SaveChangesAsync();

        var dtos = new[] { entity.Adapt<WellOperationDto>() };

        //act
        var response = await client.UpdateRangeAsync(well.Id, dtos);

        //assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }

    /// <summary>
    /// Ïîëó÷åíèå ïëàíîâûõ îïåðàöèé
    /// </summary>
    /// <returns></returns>
    [Fact]
    public async Task GetPageOperationsAsync_returns_first_page()
    {
        //arrange
        const int pageSize = 10;
        const int pageIndex = 0;

        var well = await dbContext.Wells.FirstAsync();
        var entity = CreateWellOperation(well.Id);
        dbContext.WellOperations.Add(entity);
        await dbContext.SaveChangesAsync();

        var dto = entity.Adapt<WellOperationDto>();
        var timezoneOffset = TimeSpan.FromHours(well.Timezone.Hours);
        dto.DateStart = dto.DateStart.ToOffset(timezoneOffset);
        dto.LastUpdateDate = dto.LastUpdateDate?.ToOffset(timezoneOffset);

        var request = new WellOperationRequestBase
        {
            OperationType = WellOperation.IdOperationTypePlan,
            Skip = pageIndex,
            Take = pageSize,
        };

        //act
        var response = await client.GetPageOperationsAsync(well.Id, request);

        //assert
        Assert.Equal(response.StatusCode, HttpStatusCode.OK);
        Assert.NotNull(response.Content);

        var totalExpected = response.Content.Count - pageSize * pageIndex;
        Assert.Equal(totalExpected, response.Content.Items.Count());

        Assert.Single(response.Content.Items);
        var actualDto = response.Content.Items.First();

        MatchHelper.Match(dto, actualDto);
    }

    [Theory]
    [InlineData(WellOperation.IdOperationTypePlan, "PlanWellOperations.xlsx")]
    [InlineData(WellOperation.IdOperationTypeFact, "FactWellOperations.xlsx")]
    public async Task ParseAsync_returns_success(int idType, string fileName)
    {
        //arrange
        var well = await dbContext.Wells.FirstAsync();

        var expectedDto = new WellOperationDto
        {
            IdWell = well.Id,
            IdWellSectionType = 2,
            IdCategory = WellOperationCategory.IdSlide,
            IdPlan = null,
            CategoryInfo = "Äîï.èíôî",
            IdType = idType,
            DepthStart = 10.0,
            DepthEnd = 20.0,
            DateStart = new DateTimeOffset(new DateTime(2023, 1, 10), TimeSpan.FromHours(well.Timezone.Hours)),
            DurationHours = 1.0,
            Comment = "123",
        };

        var stream = Assembly.GetExecutingAssembly().GetFileCopyStream(fileName);

        var streamPart = new StreamPart(stream, fileName, "application/octet-stream");

        //act
        var response = await client.ParseAsync(well.Id, idType, streamPart);

        //assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.NotNull(response.Content);

        var actualDto = response.Content.Item.First().Item;

        MatchHelper.Match(expectedDto, actualDto);
    }

    [Theory]
    [InlineData(WellOperation.IdOperationTypePlan)]
    [InlineData(WellOperation.IdOperationTypeFact)]
    public async Task ExportAsync_returns_success(int idType)
    {
        //arrange
        var well = await dbContext.Wells.FirstAsync();

        var entity = CreateWellOperation(well.Id, idType);
        dbContext.WellOperations.Add(entity);
        await dbContext.SaveChangesAsync();

        //act
        var response = await client.ExportAsync(well.Id, idType);

        //assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.Equal("application/octet-stream", response.ContentHeaders?.ContentType?.MediaType);
        Assert.True(response.ContentHeaders?.ContentLength > 0);
    }

    [Theory]
    [InlineData(WellOperation.IdOperationTypePlan)]
    [InlineData(WellOperation.IdOperationTypeFact)]
    public async Task MatchCategoriesBetweenTemplateAndDb_returns_success(int idType)
    {
        //arrange
        var well = await dbContext.Wells.FirstAsync();
        var responseTemplate = await client.GetTemplate(well.Id, idType);
        var stream = responseTemplate.Content;

        using var workbook = new XLWorkbook(stream);
        var sheet = workbook.GetWorksheet("Ñïðàâî÷íèêè");

        var count = sheet.RowsUsed().Count() - 1;

        var categoryCaptionsInTemplate = new List<string>();
        for (var i = 0; i < count; i++)
        {
            var xlRow = sheet.Row(i + 2);
            var rowNumber = xlRow.RowNumber();

            var xlCell = xlRow.Cell(1);
            var cellValue = xlCell.GetText().Trim();

            categoryCaptionsInTemplate.Add(cellValue);
        }

        var responseCategories = await client.GetCategories(well.Id, false, false);
        var categories = responseCategories.Content;
        var categoryCaptionsInDb = categories!.Select(c => c.Name.Trim());

        var notExistedInTemplate = categoryCaptionsInDb.Except(categoryCaptionsInTemplate);
        var notExistedInDb = categoryCaptionsInTemplate.Except(categoryCaptionsInDb);

        //assert
        Assert.True(categoryCaptionsInDb.Count() == categoryCaptionsInTemplate.Count());
        Assert.True(notExistedInTemplate.Count() == 0);
        Assert.True(notExistedInDb.Count() == 0);
    }

    [Theory]
    [InlineData(WellOperation.IdOperationTypePlan)]
    [InlineData(WellOperation.IdOperationTypeFact)]
    public async Task GetPageOperationsAsyncWithDaysAndNpv_returns_success(int idType)
    {
        //arrange
        const int pageSize = 10;
        const int pageIndex = 0;

        var well = await dbContext.Wells.FirstAsync();
        var entity1 = CreateWellOperation(well.Id);

        var entity2 = entity1.Adapt<WellOperation>();
		entity2.DateStart = entity2.DateStart.AddDays(1);
		entity2.IdCategory = WellOperationCategory.IdEquipmentDrillingRepair;
		entity2.DurationHours = 2;

        var entity3 = entity2.Adapt<WellOperation>();
        entity3.DateStart = entity3.DateStart.AddDays(1);
        entity3.IdCategory = WellOperationCategory.IdEquipmentDrillingRepair;
        entity3.DurationHours = 3;

        dbContext.WellOperations.Add(entity1);
        dbContext.WellOperations.Add(entity2);
        dbContext.WellOperations.Add(entity3);

        await dbContext.SaveChangesAsync();

		var request = new WellOperationRequestBase
		{
			OperationType = WellOperation.IdOperationTypePlan,
			Skip = pageIndex,
			Take = pageSize,
            SortFields = [nameof(WellOperation.DateStart)]
        };

		//act
		var response = await client.GetPageOperationsAsync(well.Id, request);

        //assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.NotNull(response.Content);

		var items = response.Content.Items.ToArray();

        Assert.Equal(0, items[0].Day);
        Assert.Equal(1, items[1].Day);
        Assert.Equal(2, items[2].Day);

        Assert.Equal(0, items[0].NptHours);
        Assert.Equal(2, items[1].NptHours);
        Assert.Equal(5, items[2].NptHours);
    }

    private static WellOperation CreateWellOperation(int idWell, int idType = WellOperation.IdOperationTypePlan) =>
		new()
		{
			IdWell = idWell,
			IdWellSectionType = 2,
			IdCategory = WellOperationCategory.IdSlide,
			IdPlan = null,
			CategoryInfo = "Äîï.èíôî",
			LastUpdateDate = new DateTimeOffset(new DateTime(2023, 1, 10)).ToUniversalTime(),
			IdType = idType,
			DepthStart = 10.0,
			DepthEnd = 20.0,
			DateStart = new DateTimeOffset(new DateTime(2023, 1, 10), TimeSpan.FromHours(Defaults.Timezone.Hours)).ToUniversalTime(),
			DurationHours = 1.0,
			Comment = "1",
			IdUser = 1,
		};
}