using AsbCloudApp.Data.ProcessMapPlan;
using AsbCloudApp.Requests;
using AsbCloudDb.Model.ProcessMapPlan;
using AsbCloudDb.Model.ProcessMaps;
using AsbCloudWebApi.IntegrationTests.Clients;
using Mapster;
using Microsoft.EntityFrameworkCore;
using System.Net;
using Xunit;

namespace AsbCloudWebApi.IntegrationTests.Controllers;

public class ProcessMapPlanDrillingControllerTest: BaseIntegrationTest
{
    private IProcessMapPlanDrillingClient client;
    private readonly ProcessMapPlanDrillingDto dto = new (){
        Id = 0,
        IdAuthor = 0,
        IdEditor = null,
        Creation = new(),
        Obsolete = null,
        IdState = 0,
        IdPrevious = null,

        IdWell = 1,
        IdWellSectionType = 1,
        DepthStart = 0.5,
        DepthEnd = 1.5,

        IdMode = 1,
        AxialLoadPlan = 2.718281,
        AxialLoadLimitMax = 3.1415926,
        DeltaPressurePlan = 4,
        DeltaPressureLimitMax = 5,
        TopDriveTorquePlan = 6,
        TopDriveTorqueLimitMax = 7,
        TopDriveSpeedPlan = 8,
        TopDriveSpeedLimitMax = 9,
        FlowPlan = 10,
        FlowLimitMax = 11,
        RopPlan = 12,
        UsageSaub = 13,
        UsageSpin = 14,
        Comment = "это тестовая запись",
    };
    private readonly ProcessMapPlanDrilling entity = new ()
    {
        Id = 0,
        IdAuthor = 1,
        IdEditor = null,
        Creation = DateTimeOffset.UtcNow,
        Obsolete = null,
        IdState = AsbCloudDb.Model.ChangeLogAbstract.IdStateActual,
        IdPrevious = null,

        IdWell = 1,
        IdWellSectionType = 1,
        DepthStart = 0.5,
        DepthEnd = 1.5,

        IdMode = 1,
        AxialLoadPlan = 2.718281,
        AxialLoadLimitMax = 3.1415926,
        DeltaPressurePlan = 4,
        DeltaPressureLimitMax = 5,
        TopDriveTorquePlan = 6,
        TopDriveTorqueLimitMax = 7,
        TopDriveSpeedPlan = 8,
        TopDriveSpeedLimitMax = 9,
        FlowPlan = 10,
        FlowLimitMax = 11,
        RopPlan = 12,
        UsageSaub = 13,
        UsageSpin = 14,
        Comment = "это тестовая запись",
    };

    public ProcessMapPlanDrillingControllerTest(WebAppFactoryFixture factory) : base(factory)
    {
        dbContext.CleanupDbSet<ProcessMapPlanDrilling>();
        client = factory.GetAuthorizedHttpClient<IProcessMapPlanDrillingClient>();
    }

    [Fact]
    public async Task InsertRange_returns_success()
    {
        //arrange
        var expected = dto.Adapt<ProcessMapPlanDrillingDto>();

        //act
        var response = await client.InsertRange(dto.IdWell, new[] { expected });
        
        //assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.Equal(1, response.Content);

        var entity = dbContext
            .Set<ProcessMapPlanDrilling>()
            .Where(p => p.AxialLoadPlan == dto.AxialLoadPlan)
            .Where(p => p.AxialLoadLimitMax == dto.AxialLoadLimitMax)
            .Where(p => p.Comment == dto.Comment)
            .FirstOrDefault(p => p.IdWell == dto.IdWell);

        Assert.NotNull(entity);

        var actual = entity.Adapt<ProcessMapPlanDrillingDto>();
        Assert.Equal(ProcessMapPlanBase.IdStateActual, actual.IdState);

        var excludeProps = new[] {
            nameof(ProcessMapPlanDrillingDto.Id),
            nameof(ProcessMapPlanDrillingDto.IdState),
            nameof(ProcessMapPlanDrillingDto.IdAuthor),
            nameof(ProcessMapPlanDrillingDto.Creation),
        };
        MatchHelper.Match(expected, actual, excludeProps);
    }

    [Fact]
    public async Task InsertRange_returns_BadRequest_for_IdWellSectionType()
    {
        //arrange
        var badDto = dto.Adapt<ProcessMapPlanDrillingDto>();
        badDto.IdWellSectionType = int.MaxValue;

        //act
        var response = await client.InsertRange(dto.IdWell, new[] { badDto });

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

    [Fact]
    public async Task InsertRange_returns_BadRequest_for_IdMode()
    {
        //arrange
        var badDto = dto.Adapt<ProcessMapPlanDrillingDto>();
        badDto.IdMode = int.MaxValue;

        //act
        var response = await client.InsertRange(dto.IdWell, new[] { badDto });

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

    [Fact]
    public async Task InsertRange_returns_BadRequest_for_IdWell()
    {
        //arrange
        var badDto = dto.Adapt<ProcessMapPlanDrillingDto>();
        badDto.IdWell = int.MaxValue;

        //act
        var response = await client.InsertRange(dto.IdWell, new[] { badDto });

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

    [Fact]
    public async Task ClearAndInsertRange_returns_success()
    {
        // arrange
        var dbset = dbContext.Set<ProcessMapPlanDrilling>();
        
        var entry = dbset.Add(entity);
        dbContext.SaveChanges();
        entry.State = EntityState.Detached;

        var startTime = DateTimeOffset.UtcNow;
        
        // act
        var result = await client.ClearAndInsertRange(entity.IdWell, new ProcessMapPlanDrillingDto[] { dto });

        // assert
        var doneTime = DateTimeOffset.UtcNow;
        Assert.Equal(HttpStatusCode.OK, result.StatusCode);
        Assert.Equal(2, result.Content);

        var count = dbset.Count();
        Assert.Equal(2, count);

        var oldEntity = dbset.First(p => p.Id == entry.Entity.Id);
        Assert.Equal(ProcessMapPlanBase.IdClearedOnImport, oldEntity.IdState);
        Assert.Equal(1, oldEntity.IdEditor);
        Assert.NotNull(oldEntity.Obsolete);
        Assert.InRange(oldEntity.Obsolete.Value, startTime, doneTime);

        var newEntity = dbset.First(p => p.Id != entry.Entity.Id);
        Assert.Equal(ProcessMapPlanBase.IdStateActual, newEntity.IdState);
        Assert.Equal(1, newEntity.IdAuthor);
        Assert.Null(newEntity.IdEditor);
        Assert.Null(newEntity.Obsolete);
        Assert.Null(newEntity.IdPrevious);
        Assert.InRange(newEntity.Creation, startTime, doneTime);
    }

    [Fact]
    public async Task UpdateRange_returns_success()
    {
        // arrange
        var startTime = DateTimeOffset.UtcNow;
        
        var dbset = dbContext.Set<ProcessMapPlanDrilling>();
        
        var entry = dbset.Add(entity);
        dbContext.SaveChanges();
        entry.State = EntityState.Detached;

        var dtoCopy = dto.Adapt<ProcessMapPlanDrillingDto>();
        dtoCopy.Id = entry.Entity.Id;
        dtoCopy.Comment = "nebuchadnezzar";
        dtoCopy.DeltaPressureLimitMax ++;
        dtoCopy.DeltaPressurePlan ++;
        dtoCopy.FlowPlan ++;
        dtoCopy.FlowLimitMax ++;
        dtoCopy.RopPlan ++;
        dtoCopy.AxialLoadPlan ++;
        dtoCopy.AxialLoadLimitMax ++;
        dtoCopy.DepthStart ++;
        dtoCopy.DepthEnd ++;
        dtoCopy.TopDriveSpeedPlan ++;
        dtoCopy.TopDriveSpeedLimitMax ++;
        dtoCopy.TopDriveTorquePlan ++;
        dtoCopy.TopDriveTorqueLimitMax ++;

        // act
        var result = await client.UpdateRangeAsync(entity.IdWell, new ProcessMapPlanDrillingDto[] { dtoCopy });

        // assert
        var doneTime = DateTimeOffset.UtcNow;
        Assert.Equal(HttpStatusCode.OK, result.StatusCode);
        Assert.Equal(2, result.Content);

        var count = dbset.Count();
        Assert.Equal(2, count);

        var oldEntity = dbset.First(p => p.Id == entry.Entity.Id);
        Assert.Equal(ProcessMapPlanBase.IdStateReplaced, oldEntity.IdState);
        Assert.Equal(1, oldEntity.IdEditor);
        Assert.NotNull(oldEntity.Obsolete);
        Assert.InRange(oldEntity.Obsolete.Value, startTime, doneTime);

        var newEntity = dbset.First(p => p.Id != entry.Entity.Id);
        Assert.Equal(ProcessMapPlanBase.IdStateActual, newEntity.IdState);
        Assert.Equal(1, newEntity.IdAuthor);
        Assert.Null(newEntity.IdEditor);
        Assert.Null(newEntity.Obsolete);
        Assert.Equal(oldEntity.Id, newEntity.IdPrevious);
        Assert.InRange(newEntity.Creation, startTime, doneTime);

        var expected = dtoCopy.Adapt<ProcessMapPlanDrilling>();
        var excludeProps = new[] {
            nameof(ProcessMapPlanDrillingDto.Id),
            nameof(ProcessMapPlanDrillingDto.IdAuthor),
            nameof(ProcessMapPlanDrillingDto.Creation),
        };
        MatchHelper.Match(expected, newEntity!, excludeProps);
    }

    [Fact]
    public async Task DeleteRange_returns_success()
    {
        //arrange
        var dbset = dbContext.Set<ProcessMapPlanDrilling>();
        
        var entry = dbset.Add(entity);
        dbContext.SaveChanges();
        entry.State = EntityState.Detached;

        var startTime = DateTimeOffset.UtcNow;
        
        //act
        var response = await client.DeleteRange(dto.IdWell, new[] { entry.Entity.Id });

        //assert
        var doneTime = DateTimeOffset.UtcNow;
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.Equal(1, response.Content);

        var actual = dbContext
            .Set<ProcessMapPlanDrilling>()
            .FirstOrDefault(p => p.Id == entry.Entity.Id);

        Assert.NotNull(actual);
        Assert.Equal(ProcessMapPlanBase.IdStateDeleted, actual.IdState);
        Assert.Equal(1, actual.IdEditor);
        Assert.NotNull(actual.Obsolete);
        Assert.InRange(actual.Obsolete.Value, startTime, doneTime);
    }

    [Fact]
    public async Task GetDatesChange_returns_success()
    {
        //arrange
        var dbset = dbContext.Set<ProcessMapPlanDrilling>();
        
        var entity2 = entity.Adapt<ProcessMapPlanDrilling>();
        entity2.Creation = entity.Creation.AddDays(1);
        dbset.Add(entity);
        dbset.Add(entity2);
        dbContext.SaveChanges();
        var timezoneHours = Data.Defaults.Wells[0].Timezone.Hours;
        var offset = TimeSpan.FromHours(timezoneHours);
        var dates = new[] { entity.Creation, entity2.Creation }
            .Select(d => d.ToOffset(offset))
            .Select(d => new DateOnly(d.Year, d.Month, d.Day));

        //act
        var response = await client.GetDatesChange(dto.IdWell);

        //assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.NotNull(response.Content);
        Assert.Equal(2, response.Content.Count());
        Assert.All(response.Content, d => dates.Contains(d));
    }
    
    [Fact]
    public async Task Get_all_returns_success()
    {
        //arrange
        var dbset = dbContext.Set<ProcessMapPlanDrilling>();
        
        dbset.Add(entity);

        var entityDeleted = entity.Adapt<ProcessMapPlanDrilling>();
        entityDeleted.Creation = entity.Creation.AddDays(-1);
        entityDeleted.Obsolete = entity.Creation;
        entityDeleted.IdState = ProcessMapPlanBase.IdStateDeleted;
        entityDeleted.IdEditor = 1;
        dbset.Add(entityDeleted);

        dbContext.SaveChanges();

        //act
        var request = new ProcessMapPlanBaseRequest();
        var response = await client.Get(dto.IdWell, request);

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

    [Fact]
    public async Task Get_actual_returns_success()
    {
        //arrange
        var dbset = dbContext.Set<ProcessMapPlanDrilling>();
        
        dbset.Add(entity);

        var entityDeleted = entity.Adapt<ProcessMapPlanDrilling>();
        entityDeleted.Creation = entity.Creation.AddDays(-1);
        entityDeleted.Obsolete = entity.Creation;
        entityDeleted.IdState = ProcessMapPlanBase.IdStateDeleted;
        entityDeleted.IdEditor = 1;
        entityDeleted.Comment = "nothing";
        dbset.Add(entityDeleted);

        dbContext.SaveChanges();

        //act
        var request = new ProcessMapPlanBaseRequest {
            Moment = new DateTimeOffset(3000, 1, 1, 0, 0, 0, 0, TimeSpan.Zero) 
        };
        var response = await client.Get(dto.IdWell, request);

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

        var actual = response.Content.First()!;

        var expected = entity.Adapt<ProcessMapPlanDrillingDto>()!;
        var excludeProps = new[] {
            nameof(ProcessMapPlanDrillingDto.Id),
            nameof(ProcessMapPlanDrillingDto.IdAuthor),
            nameof(ProcessMapPlanDrillingDto.Creation),
        };
        MatchHelper.Match(expected, actual, excludeProps);
    }

    [Fact]
    public async Task Get_at_moment_returns_success()
    {
        //arrange
        var dbset = dbContext.Set<ProcessMapPlanDrilling>();
        
        var now = DateTimeOffset.UtcNow;
        var entityDeleted = entity.Adapt<ProcessMapPlanDrilling>();
        entityDeleted.Creation = now;
        entityDeleted.Obsolete = now.AddMinutes(1);
        entityDeleted.IdState = ProcessMapPlanBase.IdStateDeleted;
        entityDeleted.IdEditor = 1;
        entityDeleted.Comment = "nothing";
        dbset.Add(entityDeleted);

        var entityDeleted2 = entity.Adapt<ProcessMapPlanDrilling>();
        entityDeleted2.Creation = now.AddMinutes(1);
        entityDeleted2.Obsolete = now.AddMinutes(2);
        entityDeleted2.IdState = ProcessMapPlanBase.IdStateDeleted;
        entityDeleted2.IdEditor = 1;
        entityDeleted2.Comment = "nothing";
        dbset.Add(entityDeleted2);

        dbContext.SaveChanges();

        //act
        var request = new ProcessMapPlanBaseRequest
        {
            Moment = now.AddMinutes(0.5),
        };
        var response = await client.Get(dto.IdWell, request);

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

    [Fact]
    public async Task Get_section_returns_success()
    {
        //arrange
        var dbset = dbContext.Set<ProcessMapPlanDrilling>();
        
        dbset.Add(entity);

        var entity2 = entity.Adapt<ProcessMapPlanDrilling>();
        entity2.IdWellSectionType = 2;
        entity2.Comment = "IdWellSectionType = 2";
        dbset.Add(entity2);

        dbContext.SaveChanges();

        //act
        var request = new ProcessMapPlanBaseRequest
        {
            IdWellSectionType = 2,
        };
        var response = await client.Get(dto.IdWell, request);

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

        var actual = response.Content.First()!;

        var expected = entity2.Adapt<ProcessMapPlanDrillingDto>()!;
        var excludeProps = new[] {
            nameof(ProcessMapPlanDrillingDto.Id),
            nameof(ProcessMapPlanDrillingDto.IdAuthor),
            nameof(ProcessMapPlanDrillingDto.Creation),
        };
        MatchHelper.Match(expected, actual, excludeProps);
    }

    [Fact]
    public async Task Get_updated_returns_success()
    {
        //arrange
        var dbset = dbContext.Set<ProcessMapPlanDrilling>();
        
        dbset.Add(entity);

        var entity2 = entity.Adapt<ProcessMapPlanDrilling>();
        entity2.Creation = entity.Creation.AddHours(1);
        entity2.Comment = "IdWellSectionType = 2";
        dbset.Add(entity2);

        var entity3 = entity.Adapt<ProcessMapPlanDrilling>();
        entity3.Obsolete = entity.Creation.AddHours(1);
        entity3.Comment = "IdWellSectionType = 3";
        dbset.Add(entity3);

        dbContext.SaveChanges();

        var timezoneHours = Data.Defaults.Wells[0].Timezone.Hours;
        var offset = TimeSpan.FromHours(timezoneHours);
        var updateFrom = entity.Creation.ToOffset(offset).AddHours(0.5);

        //act
        var request = new ProcessMapPlanBaseRequest
        {
            UpdateFrom = updateFrom,
        };
        var response = await client.Get(dto.IdWell, request);

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

        var actual = response.Content
            .First(p => p.Comment == entity2.Comment);

        var expected = entity2.Adapt<ProcessMapPlanDrillingDto>();
        var excludeProps = new[] {
            nameof(ProcessMapPlanDrillingDto.Id),
            nameof(ProcessMapPlanDrillingDto.IdAuthor),
            nameof(ProcessMapPlanDrillingDto.Creation),
        };
        MatchHelper.Match(expected, actual, excludeProps);
    }
}