{
  [Collection("GraphQL")]
  public class EventExcludedArtistsTests
  {
    private readonly GraphQLHttpClient _graphQLClient;
    private readonly AreaDbHelper _areaDbHelper;
    private readonly UserDbHelper _userDbHelper;
    private readonly VenueDbHelper _venueDbHelper;
    private readonly ArtistDbHelper _artistDbHelper;

    private readonly EventDbHelper _eventDbHelper;
    private readonly ExcludedArtistsDbHelper _excludedArtistDbHelper;
    private readonly IDbConnectionFactory _dbConnectionFactory;

    public EventExcludedArtistsTests(GraphQLFixture fixture)
    {
      var userId = CreateUser(fixture).Result;
      _graphQLClient = fixture.WebApplicationFactory
        .CreateGraphQLHttpClient()
        .SetAuthenticatedUser(userId)
        .SetHeader("Cache-Control", "no-cache");

      _areaDbHelper = fixture.WebApplicationFactory.GetDbHelper<AreaDbHelper>();

      _userDbHelper = fixture.WebApplicationFactory.GetDbHelper<UserDbHelper>();
      _venueDbHelper = fixture.WebApplicationFactory.GetDbHelper<VenueDbHelper>();
      _artistDbHelper = fixture.WebApplicationFactory.GetDbHelper<ArtistDbHelper>();
      _eventDbHelper = fixture.WebApplicationFactory.GetDbHelper<EventDbHelper>();
      _excludedArtistDbHelper =
        fixture.WebApplicationFactory.GetDbHelper<ExcludedArtistsDbHelper>();
      _dbConnectionFactory = fixture.WebApplicationFactory.DbConnectionFactory;
    }

    private async Task<int> CreateUser(GraphQLFixture fixture)
    {
      var userDbHelper = fixture.WebApplicationFactory.GetDbHelper<UserDbHelper>();
      var areaDbHelper = fixture.WebApplicationFactory.GetDbHelper<AreaDbHelper>();
      var area = await areaDbHelper.Insert();
      var user = await userDbHelper.Insert(
        user => user.WithEventAccess(EventAccessLevelType.StandardUser).WithAreaId(area.Id)
      );
      return user.Id;
    }

    [Fact]
    public async Task ExcludedArtistsReturnedOnEvent()
    {
      // Arrange
      var venueId = await InsertPrequisiteData();

      var artist = await _artistDbHelper.Insert();
      var excludedArtist = await _artistDbHelper.Insert();

      // Create event with the artists
      var eventId = await InsertEventInDb(venueId, GenerateLineup(artist, excludedArtist));

      // Exclude exludedArtist
      await _excludedArtistDbHelper.InsertExcludedArtist(excludedArtist.Id, eventId);

      // Act - get the event
      var @event = await GetEvent(eventId);

      // Assert
      var excludedArtistActual = Assert.Single(@event.ExcludedArtists);
      Assert.Equal(excludedArtist.Id.ToString(), excludedArtistActual.Id);
    }

    [Fact]
    public async Task ExcludedArtistsFilteredFromLineupAndRevisionsLineup()
    {
      // Arrange
      var venueId = await InsertPrequisiteData();

      var artist = await _artistDbHelper.Insert();
      var excludedArtist = await _artistDbHelper.Insert();
      string inputLineup = GenerateLineup(artist, excludedArtist);
      string expectedLineup = GenerateLineup(artist) + $" and {excludedArtist.Name}";

      // Create event with the artists
      var eventId = await InsertEventInDb(venueId, inputLineup);

      // Exclude exludedArtist
      await _excludedArtistDbHelper.InsertExcludedArtist(excludedArtist.Id, eventId);

      // Act - get the event
      var @event = await GetEvent(eventId);

      // Assert
      Assert.Equal(expectedLineup, @event.Lineup);

      var revision = Assert.Single(@event.Revisions);
      Assert.Equal(HttpUtility.HtmlDecode(expectedLineup), revision.Event.Lineup);
    }

    [Fact]
    public async Task ExcludedArtistsFilteredFromEventArtistsAndRevisionsArtistsArrays()
    {
      // Arrange
      var venueId = await InsertPrequisiteData();

      var includedArtist = await _artistDbHelper.Insert();
      var excludedArtist = await _artistDbHelper.Insert();
      string inputLineup = GenerateLineup(includedArtist, excludedArtist);

      // Create event with the artists
      var eventId = await InsertEventInDb(venueId, inputLineup);

      // Exclude exludedArtist
      await _excludedArtistDbHelper.InsertExcludedArtist(excludedArtist.Id, eventId);

      // Act - get the event
      var @event = await GetEvent(eventId);

      // Assert
      var actualArtist = Assert.Single(@event.Artists);
      Assert.Equal(includedArtist.Id.ToString(), actualArtist.Id);

      var revision = Assert.Single(@event.Revisions);
      var revisionArtist = Assert.Single(revision.Event.Artists);
      Assert.Equal(includedArtist.Id.ToString(), revisionArtist.Id);
    }

    [Fact]
    public async Task ArtistsRemainExcludedWhenEventRevisionIsSet()
    {
      // Arrange

      var venueId = await InsertPrequisiteData();

      var includedArtist = await _artistDbHelper.Insert();
      var excludedArtist = await _artistDbHelper.Insert();
      string inputLineup = GenerateLineup(includedArtist, excludedArtist);

      // Create event with the artists
      int eventId = await InsertEventInDb(venueId, inputLineup);

      // Exclude exludedArtist
      await _excludedArtistDbHelper.InsertExcludedArtist(excludedArtist.Id, eventId);

      var @event = await GetEvent(eventId);

      // Event revision
      var eventRevisionInput = new EventInputDto
      {
        VenueId = venueId,
        Title = "Event Tile Updated",
        StartTime = new DateTime(2022, 2, 22),
        EndTime = new DateTime(2022, 2, 23),
        Lineup = inputLineup,
        Visibility = VisibilityType.Public
      };

      // Act
      await EventRevisionMutationTestHelpers.CreateEventRevisionMutation(
        _graphQLClient,
        eventRevisionInput,
        int.Parse(@event.Id)
      );

      // Assert
      @event = await GetEvent(eventId);

      Assert.Equal(2, @event.Revisions.Count());
      Assert.Collection(
        @event.Revisions,
        item1 =>
        {
          var a1 = Assert.Single(item1.Event.Artists);
          Assert.Equal(includedArtist.Id.ToString(), a1.Id);
        },
        item2 =>
        {
          var a2 = Assert.Single(item2.Event.Artists);
          Assert.Equal(includedArtist.Id.ToString(), a2.Id);
        }
      );

      // Check the DB too
      var eventArtists = await DbHelper.GetAsync<TblEventArtist>(
        _dbConnectionFactory,
        t => t.EventId == eventId
      );

      var eventArtistInDb = Assert.Single(eventArtists);
      Assert.Equal(includedArtist.Id, eventArtistInDb.DJID);
    }

    private async Task<EventResponse> GetEvent(int eventId)
    {
      var request = GetGraphQLRequest(eventId);
      var eventResponse = await _graphQLClient.SendQueryAsync(
        request,
        () => new { eventResponse = new EventResponse() }
      );

      eventResponse.AssertGraphQLWasSuccessful();
      return eventResponse.Data.eventResponse;
    }

    private static GraphQLRequest GetGraphQLRequest(int eventId) =>
      new GraphQLRequest
      {
        Query =
          @"
          query GET_EVENT($eventId: ID!) {
            eventResponse: event(id: $eventId) {
              id
              lineup
              datePosted
              artists {
                id
                name
              }
              excludedArtists {
                id
                name
              }
              revisions {
                id
                event {
                  lineup
                  artists {
                    id
                  }
                }
              }
            }
          }
        ",
        Variables = new { eventId }
      };

    private async Task<int> InsertEventInDb(int venueId, string lineup)
    {
      var eventInputDto = new EventInputDto
      {
        VenueId = venueId,
        Title = "Event Tile",
        StartTime = new DateTime(2022, 2, 22),
        EndTime = new DateTime(2022, 2, 23),
        Lineup = lineup,
        SubmissionSource = EventSubmissionSource.SubmitEventPage,
        Visibility = VisibilityType.Public,
      };

      var @event = await EventMutationTestHelpers.CreateEventMutation(
        _graphQLClient,
        eventInputDto
      );
      return int.Parse(@event.Data.Id);
    }

    private async Task<int> InsertPrequisiteData()
    {
      var areaDto = await _areaDbHelper.Insert();
      var userDto = await _userDbHelper.Insert(options => options.WithAreaId(areaDto.Id));
      var venueDto = await _venueDbHelper.Insert(
        options => options.WithUserId(userDto.Id).WithAreaId(areaDto.Id)
      );
      var eventDto = await _eventDbHelper.Insert(
        e => e.WithVenueId(venueDto.Id).WithDate(DateTime.UtcNow)
      );

      return eventDto.VenueId;
    }

    private static string GenerateLineup(params ArtistDto[] artists) =>
      string.Join(" and ", artists.Select(a => $"<artist id=\"{a.Id}\">{a.Name}</artist>"));

    private class EventResponse : IdResponse
    {
      public string Lineup { get; set; }
      public IEnumerable<ArtistResponse> Artists { get; set; }
      public IEnumerable<ArtistResponse> ExcludedArtists { get; set; }
      public IEnumerable<EventResponse> DatePosted { get; set; }
      public IEnumerable<RevisionResponse> Revisions { get; set; }
    }

    private class RevisionResponse : IdResponse
    {
      public EventResponse Event { get; set; }
    }

    private class ArtistResponse : IdResponse
    {
      public string Name { get; set; }
    }

    private class IdResponse
    {
      public string Id { get; set; }
    }

    [Table("tblEventsArtist")]
    private class TblEventArtist
    {
      public int EventId { get; set; }
      public int DJID { get; set; }
    }
  }
}