#[cfg(test)]
mod tests {
  use std::sync::Arc;

  use clap::{CommandFactory, error::ErrorKind};
  use mockall::predicate::eq;
  use rstest::rstest;
  use serde_json::json;
  use tokio::sync::Mutex;

  use crate::cli::lidarr::LidarrCommand;
  use crate::network::lidarr_network::LidarrEvent;
  use crate::{
    Cli,
    app::App,
    cli::{handle_command, mutex_flags_or_option, radarr::RadarrCommand, sonarr::SonarrCommand},
    models::{
      Serdeable,
      lidarr_models::{
        BlocklistItem as LidarrBlocklistItem, BlocklistResponse as LidarrBlocklistResponse,
        LidarrSerdeable,
      },
      radarr_models::{
        BlocklistItem as RadarrBlocklistItem, BlocklistResponse as RadarrBlocklistResponse,
        RadarrSerdeable,
      },
      sonarr_models::{
        BlocklistItem as SonarrBlocklistItem, BlocklistResponse as SonarrBlocklistResponse,
        SonarrSerdeable,
      },
    },
    network::{
      MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent, sonarr_network::SonarrEvent,
    },
  };
  use pretty_assertions::assert_eq;

  #[rstest]
  fn test_servarr_subcommand_requires_subcommand(#[values("radarr", "sonarr")] subcommand: &str) {
    let result = Cli::command().try_get_matches_from(["managarr", subcommand]);

    assert_err!(&result);
    assert_eq!(
      result.unwrap_err().kind(),
      ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
    );
  }

  #[test]
  fn test_radarr_subcommand_delegates_to_radarr() {
    let result =
      Cli::command().try_get_matches_from(["managarr", "radarr", "get", "all-indexer-settings"]);

    assert_ok!(&result);
  }

  #[test]
  fn test_sonarr_subcommand_delegates_to_sonarr() {
    let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "series"]);

    assert_ok!(&result);
  }

  #[test]
  fn test_lidarr_subcommand_delegates_to_lidarr() {
    let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "list", "artists"]);

    assert_ok!(&result);
  }

  #[test]
  fn test_completions_requires_argument() {
    let result = Cli::command().try_get_matches_from(["managarr", "completions"]);

    assert_err!(&result);
    assert_eq!(
      result.unwrap_err().kind(),
      ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
    );
  }

  #[test]
  fn test_completions_invalid_argument() {
    let result = Cli::command().try_get_matches_from(["managarr", "completions", "test"]);

    assert_err!(&result);
    assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
  }

  #[test]
  fn test_completions_satisfied_with_argument() {
    let result = Cli::command().try_get_matches_from(["managarr", "completions", "bash"]);

    assert_ok!(&result);
  }

  #[rstest]
  #[case(false, false, None)]
  #[case(false, true, Some(false))]
  #[case(true, false, Some(true))]
  fn test_mutex_flags_or_option(
    #[case] positive: bool,
    #[case] negative: bool,
    #[case] expected_output: Option<bool>,
  ) {
    let result = mutex_flags_or_option(positive, negative);

    assert_eq!(result, expected_output);
  }

  #[rstest]
  #[case(false, false, true, true)]
  #[case(false, false, false, false)]
  #[case(false, true, true, false)]
  #[case(true, false, false, true)]
  fn test_mutex_flags_or_default(
    #[case] positive: bool,
    #[case] negative: bool,
    #[case] default_value: bool,
    #[case] expected_output: bool,
  ) {
    use crate::cli::mutex_flags_or_default;

    let result = mutex_flags_or_default(positive, negative, default_value);

    assert_eq!(result, expected_output);
  }

  #[tokio::test]
  async fn test_cli_handler_delegates_radarr_commands_to_the_radarr_cli_handler() {
    let mut mock_network = MockNetworkTrait::new();
    mock_network
      .expect_handle_network_event()
      .with(eq::<NetworkEvent>(RadarrEvent::GetBlocklist.into()))
      .times(1)
      .returning(|_| {
        Ok(Serdeable::Radarr(RadarrSerdeable::BlocklistResponse(
          RadarrBlocklistResponse {
            records: vec![RadarrBlocklistItem::default()],
          },
        )))
      });
    mock_network
      .expect_handle_network_event()
      .with(eq::<NetworkEvent>(RadarrEvent::ClearBlocklist.into()))
      .times(1)
      .returning(|_| {
        Ok(Serdeable::Radarr(RadarrSerdeable::Value(
          json!({"testResponse": "response"}),
        )))
      });
    let app_arc = Arc::new(Mutex::new(App::test_default()));
    let clear_blocklist_command = RadarrCommand::ClearBlocklist.into();

    let result = handle_command(&app_arc, clear_blocklist_command, &mut mock_network).await;

    assert_ok!(&result);
  }

  #[tokio::test]
  async fn test_cli_handler_delegates_sonarr_commands_to_the_sonarr_cli_handler() {
    let mut mock_network = MockNetworkTrait::new();
    mock_network
      .expect_handle_network_event()
      .with(eq::<NetworkEvent>(SonarrEvent::GetBlocklist.into()))
      .times(1)
      .returning(|_| {
        Ok(Serdeable::Sonarr(SonarrSerdeable::BlocklistResponse(
          SonarrBlocklistResponse {
            records: vec![SonarrBlocklistItem::default()],
          },
        )))
      });
    mock_network
      .expect_handle_network_event()
      .with(eq::<NetworkEvent>(SonarrEvent::ClearBlocklist.into()))
      .times(1)
      .returning(|_| {
        Ok(Serdeable::Sonarr(SonarrSerdeable::Value(
          json!({"testResponse": "response"}),
        )))
      });
    let app_arc = Arc::new(Mutex::new(App::test_default()));
    let clear_blocklist_command = SonarrCommand::ClearBlocklist.into();

    let result = handle_command(&app_arc, clear_blocklist_command, &mut mock_network).await;

    assert_ok!(&result);
  }

  #[tokio::test]
  async fn test_cli_handler_delegates_lidarr_commands_to_the_lidarr_cli_handler() {
    let mut mock_network = MockNetworkTrait::new();
    mock_network
      .expect_handle_network_event()
      .with(eq::<NetworkEvent>(LidarrEvent::GetBlocklist.into()))
      .times(1)
      .returning(|_| {
        Ok(Serdeable::Lidarr(LidarrSerdeable::BlocklistResponse(
          LidarrBlocklistResponse {
            records: vec![LidarrBlocklistItem::default()],
          },
        )))
      });
    mock_network
      .expect_handle_network_event()
      .with(eq::<NetworkEvent>(LidarrEvent::ClearBlocklist.into()))
      .times(1)
      .returning(|_| {
        Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
          json!({"testResponse": "response"}),
        )))
      });
    let app_arc = Arc::new(Mutex::new(App::test_default()));
    let clear_blocklist_command = LidarrCommand::ClearBlocklist.into();

    let result = handle_command(&app_arc, clear_blocklist_command, &mut mock_network).await;

    assert_ok!(&result);
  }
}
