use anyhow::Result;
use log::info;
use serde_json::{json, Value};

use super::{Network, NetworkEvent, NetworkResource};
use crate::{
  models::{
    servarr_models::{AddRootFolderBody, EditIndexerParams, Language, QualityProfile, Tag},
    sonarr_models::{
      AddSeriesBody, DeleteSeriesParams, EditSeriesParams, IndexerSettings,
      SonarrReleaseDownloadBody, SonarrSerdeable, SonarrTaskName,
    },
  },
  network::RequestMethod,
};
#[cfg(test)]
#[path = "sonarr_network_tests.rs"]
mod sonarr_network_tests;

#[cfg(test)]
#[path = "sonarr_network_test_utils.rs"]
mod sonarr_network_test_utils;

mod blocklist;
mod downloads;
mod history;
mod indexers;
mod library;
mod root_folders;
mod system;

#[derive(Debug, Eq, PartialEq, Clone)]
pub enum SonarrEvent {
  AddRootFolder(AddRootFolderBody),
  AddSeries(AddSeriesBody),
  AddTag(String),
  ClearBlocklist,
  DeleteBlocklistItem(i64),
  DeleteDownload(i64),
  DeleteEpisodeFile(i64),
  DeleteIndexer(i64),
  DeleteRootFolder(i64),
  DeleteSeries(DeleteSeriesParams),
  DeleteTag(i64),
  DownloadRelease(SonarrReleaseDownloadBody),
  EditAllIndexerSettings(IndexerSettings),
  EditIndexer(EditIndexerParams),
  EditSeries(EditSeriesParams),
  GetAllIndexerSettings,
  GetBlocklist,
  GetDownloads(u64),
  GetHistory(u64),
  GetHostConfig,
  GetIndexers,
  GetEpisodeDetails(i64),
  GetEpisodes(i64),
  GetEpisodeFiles(i64),
  GetEpisodeHistory(i64),
  GetLanguageProfiles,
  GetLogs(u64),
  GetDiskSpace,
  GetQualityProfiles,
  GetQueuedEvents,
  GetRootFolders,
  GetEpisodeReleases(i64),
  GetSeasonHistory((i64, i64)),
  GetSeasonReleases((i64, i64)),
  GetSecurityConfig,
  GetSeriesDetails(i64),
  GetSeriesHistory(i64),
  GetStatus,
  GetUpdates,
  GetTags,
  GetTasks,
  HealthCheck,
  ListSeries,
  MarkHistoryItemAsFailed(i64),
  SearchNewSeries(String),
  StartTask(SonarrTaskName),
  TestIndexer(i64),
  TestAllIndexers,
  ToggleSeasonMonitoring((i64, i64)),
  ToggleSeriesMonitoring(i64),
  ToggleEpisodeMonitoring(i64),
  TriggerAutomaticEpisodeSearch(i64),
  TriggerAutomaticSeasonSearch((i64, i64)),
  TriggerAutomaticSeriesSearch(i64),
  UpdateAllSeries,
  UpdateAndScanSeries(i64),
  UpdateDownloads,
}

impl NetworkResource for SonarrEvent {
  fn resource(&self) -> &'static str {
    match &self {
      SonarrEvent::AddTag(_) | SonarrEvent::DeleteTag(_) | SonarrEvent::GetTags => "/tag",
      SonarrEvent::ClearBlocklist => "/blocklist/bulk",
      SonarrEvent::DownloadRelease(_) => "/release",
      SonarrEvent::DeleteBlocklistItem(_) => "/blocklist",
      SonarrEvent::GetAllIndexerSettings | SonarrEvent::EditAllIndexerSettings(_) => {
        "/config/indexer"
      }
      SonarrEvent::GetEpisodeFiles(_) | SonarrEvent::DeleteEpisodeFile(_) => "/episodefile",
      SonarrEvent::GetBlocklist => "/blocklist?page=1&pageSize=10000",
      SonarrEvent::GetDownloads(_) | SonarrEvent::DeleteDownload(_) => "/queue",
      SonarrEvent::GetEpisodes(_) | SonarrEvent::GetEpisodeDetails(_) => "/episode",
      SonarrEvent::GetHistory(_) | SonarrEvent::GetEpisodeHistory(_) => "/history",
      SonarrEvent::GetHostConfig | SonarrEvent::GetSecurityConfig => "/config/host",
      SonarrEvent::GetIndexers | SonarrEvent::DeleteIndexer(_) | SonarrEvent::EditIndexer(_) => {
        "/indexer"
      }
      SonarrEvent::GetLanguageProfiles => "/language",
      SonarrEvent::GetLogs(_) => "/log",
      SonarrEvent::GetDiskSpace => "/diskspace",
      SonarrEvent::GetQualityProfiles => "/qualityprofile",
      SonarrEvent::GetQueuedEvents
      | SonarrEvent::StartTask(_)
      | SonarrEvent::TriggerAutomaticSeriesSearch(_)
      | SonarrEvent::TriggerAutomaticSeasonSearch(_)
      | SonarrEvent::TriggerAutomaticEpisodeSearch(_)
      | SonarrEvent::UpdateAllSeries
      | SonarrEvent::UpdateAndScanSeries(_)
      | SonarrEvent::UpdateDownloads => "/command",
      SonarrEvent::GetRootFolders
      | SonarrEvent::DeleteRootFolder(_)
      | SonarrEvent::AddRootFolder(_) => "/rootfolder",
      SonarrEvent::GetSeasonReleases(_) | SonarrEvent::GetEpisodeReleases(_) => "/release",
      SonarrEvent::GetSeriesHistory(_) | SonarrEvent::GetSeasonHistory(_) => "/history/series",
      SonarrEvent::GetStatus => "/system/status",
      SonarrEvent::GetTasks => "/system/task",
      SonarrEvent::GetUpdates => "/update",
      SonarrEvent::HealthCheck => "/health",
      SonarrEvent::AddSeries(_)
      | SonarrEvent::ListSeries
      | SonarrEvent::GetSeriesDetails(_)
      | SonarrEvent::DeleteSeries(_)
      | SonarrEvent::EditSeries(_)
      | SonarrEvent::ToggleSeasonMonitoring(_)
      | SonarrEvent::ToggleSeriesMonitoring(_) => "/series",
      SonarrEvent::SearchNewSeries(_) => "/series/lookup",
      SonarrEvent::MarkHistoryItemAsFailed(_) => "/history/failed",
      SonarrEvent::TestIndexer(_) => "/indexer/test",
      SonarrEvent::TestAllIndexers => "/indexer/testall",
      SonarrEvent::ToggleEpisodeMonitoring(_) => "/episode/monitor",
    }
  }
}

impl From<SonarrEvent> for NetworkEvent {
  fn from(sonarr_event: SonarrEvent) -> Self {
    NetworkEvent::Sonarr(sonarr_event)
  }
}

impl Network<'_, '_> {
  pub async fn handle_sonarr_event(
    &mut self,
    sonarr_event: SonarrEvent,
  ) -> Result<SonarrSerdeable> {
    match sonarr_event {
      SonarrEvent::AddRootFolder(path) => self
        .add_sonarr_root_folder(path)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::AddSeries(body) => self
        .add_sonarr_series(body)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::AddTag(tag) => self.add_sonarr_tag(tag).await.map(SonarrSerdeable::from),
      SonarrEvent::ClearBlocklist => self
        .clear_sonarr_blocklist()
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::GetAllIndexerSettings => self
        .get_all_sonarr_indexer_settings()
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::DeleteBlocklistItem(blocklist_item_id) => self
        .delete_sonarr_blocklist_item(blocklist_item_id)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::DeleteDownload(download_id) => self
        .delete_sonarr_download(download_id)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::DeleteEpisodeFile(episode_file_id) => self
        .delete_sonarr_episode_file(episode_file_id)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::DeleteIndexer(indexer_id) => self
        .delete_sonarr_indexer(indexer_id)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::DeleteRootFolder(root_folder_id) => self
        .delete_sonarr_root_folder(root_folder_id)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::DeleteSeries(params) => {
        self.delete_series(params).await.map(SonarrSerdeable::from)
      }
      SonarrEvent::DeleteTag(tag_id) => self
        .delete_sonarr_tag(tag_id)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::DownloadRelease(sonarr_release_download_body) => self
        .download_sonarr_release(sonarr_release_download_body)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::EditAllIndexerSettings(params) => self
        .edit_all_sonarr_indexer_settings(params)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::EditIndexer(params) => self
        .edit_sonarr_indexer(params)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::EditSeries(params) => self
        .edit_sonarr_series(params)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::GetBlocklist => self.get_sonarr_blocklist().await.map(SonarrSerdeable::from),
      SonarrEvent::GetDownloads(count) => self
        .get_sonarr_downloads(count)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::GetEpisodes(series_id) => self
        .get_episodes(series_id)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::GetEpisodeFiles(series_id) => self
        .get_episode_files(series_id)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::GetEpisodeDetails(episode_id) => self
        .get_episode_details(episode_id)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::GetEpisodeHistory(episode_id) => self
        .get_sonarr_episode_history(episode_id)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::GetHistory(events) => self
        .get_sonarr_history(events)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::GetHostConfig => self
        .get_sonarr_host_config()
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::GetIndexers => self.get_sonarr_indexers().await.map(SonarrSerdeable::from),
      SonarrEvent::GetLanguageProfiles => self
        .get_sonarr_language_profiles()
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::GetLogs(events) => self
        .get_sonarr_logs(events)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::GetDiskSpace => self.get_sonarr_diskspace().await.map(SonarrSerdeable::from),
      SonarrEvent::GetQualityProfiles => self
        .get_sonarr_quality_profiles()
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::GetQueuedEvents => self
        .get_queued_sonarr_events()
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::GetRootFolders => self
        .get_sonarr_root_folders()
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::GetEpisodeReleases(params) => self
        .get_episode_releases(params)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::GetSeasonHistory(params) => self
        .get_sonarr_season_history(params)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::GetSeasonReleases(params) => self
        .get_season_releases(params)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::GetSecurityConfig => self
        .get_sonarr_security_config()
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::GetSeriesDetails(series_id) => self
        .get_series_details(series_id)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::GetSeriesHistory(series_id) => self
        .get_sonarr_series_history(series_id)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::GetStatus => self.get_sonarr_status().await.map(SonarrSerdeable::from),
      SonarrEvent::GetTags => self.get_sonarr_tags().await.map(SonarrSerdeable::from),
      SonarrEvent::GetTasks => self.get_sonarr_tasks().await.map(SonarrSerdeable::from),
      SonarrEvent::GetUpdates => self.get_sonarr_updates().await.map(SonarrSerdeable::from),
      SonarrEvent::HealthCheck => self
        .get_sonarr_healthcheck()
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::ListSeries => self.list_series().await.map(SonarrSerdeable::from),
      SonarrEvent::MarkHistoryItemAsFailed(history_item_id) => self
        .mark_sonarr_history_item_as_failed(history_item_id)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::SearchNewSeries(query) => self
        .search_sonarr_series(query)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::StartTask(task_name) => self
        .start_sonarr_task(task_name)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::TestIndexer(indexer_id) => self
        .test_sonarr_indexer(indexer_id)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::TestAllIndexers => self
        .test_all_sonarr_indexers()
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::ToggleEpisodeMonitoring(episode_id) => self
        .toggle_sonarr_episode_monitoring(episode_id)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::ToggleSeasonMonitoring(params) => self
        .toggle_sonarr_season_monitoring(params)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::ToggleSeriesMonitoring(series_id) => self
        .toggle_sonarr_series_monitoring(series_id)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::TriggerAutomaticSeasonSearch(params) => self
        .trigger_automatic_season_search(params)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::TriggerAutomaticSeriesSearch(series_id) => self
        .trigger_automatic_series_search(series_id)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::TriggerAutomaticEpisodeSearch(episode_id) => self
        .trigger_automatic_episode_search(episode_id)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::UpdateAllSeries => self.update_all_series().await.map(SonarrSerdeable::from),
      SonarrEvent::UpdateAndScanSeries(series_id) => self
        .update_and_scan_series(series_id)
        .await
        .map(SonarrSerdeable::from),
      SonarrEvent::UpdateDownloads => self
        .update_sonarr_downloads()
        .await
        .map(SonarrSerdeable::from),
    }
  }

  async fn add_sonarr_tag(&mut self, tag: String) -> Result<Tag> {
    info!("Adding a new Sonarr tag");
    let event = SonarrEvent::AddTag(String::new());

    let request_props = self
      .request_props_from(
        event,
        RequestMethod::Post,
        Some(json!({ "label": tag })),
        None,
        None,
      )
      .await;

    self
      .handle_request::<Value, Tag>(request_props, |tag, mut app| {
        app.data.sonarr_data.tags_map.insert(tag.id, tag.label);
      })
      .await
  }

  async fn delete_sonarr_tag(&mut self, id: i64) -> Result<()> {
    info!("Deleting Sonarr tag with id: {id}");
    let event = SonarrEvent::DeleteTag(id);

    let request_props = self
      .request_props_from(
        event,
        RequestMethod::Delete,
        None::<()>,
        Some(format!("/{id}")),
        None,
      )
      .await;

    self
      .handle_request::<(), ()>(request_props, |_, _| ())
      .await
  }

  async fn get_sonarr_healthcheck(&mut self) -> Result<()> {
    info!("Performing Sonarr health check");
    let event = SonarrEvent::HealthCheck;

    let request_props = self
      .request_props_from(event, RequestMethod::Get, None::<()>, None, None)
      .await;

    self
      .handle_request::<(), ()>(request_props, |_, _| ())
      .await
  }

  async fn get_sonarr_language_profiles(&mut self) -> Result<Vec<Language>> {
    info!("Fetching Sonarr language profiles");
    let event = SonarrEvent::GetLanguageProfiles;

    let request_props = self
      .request_props_from(event, RequestMethod::Get, None::<()>, None, None)
      .await;

    self
      .handle_request::<(), Vec<Language>>(request_props, |language_profiles_vec, mut app| {
        app.data.sonarr_data.language_profiles_map = language_profiles_vec
          .into_iter()
          .map(|language| (language.id, language.name))
          .collect();
      })
      .await
  }

  async fn get_sonarr_quality_profiles(&mut self) -> Result<Vec<QualityProfile>> {
    info!("Fetching Sonarr quality profiles");
    let event = SonarrEvent::GetQualityProfiles;

    let request_props = self
      .request_props_from(event, RequestMethod::Get, None::<()>, None, None)
      .await;

    self
      .handle_request::<(), Vec<QualityProfile>>(request_props, |quality_profiles, mut app| {
        app.data.sonarr_data.quality_profile_map = quality_profiles
          .into_iter()
          .map(|profile| (profile.id, profile.name))
          .collect();
      })
      .await
  }

  async fn get_sonarr_tags(&mut self) -> Result<Vec<Tag>> {
    info!("Fetching Sonarr tags");
    let event = SonarrEvent::GetTags;

    let request_props = self
      .request_props_from(event, RequestMethod::Get, None::<()>, None, None)
      .await;

    self
      .handle_request::<(), Vec<Tag>>(request_props, |tags_vec, mut app| {
        app.data.sonarr_data.tags_map = tags_vec
          .into_iter()
          .map(|tag| (tag.id, tag.label))
          .collect();
      })
      .await
  }

  pub(in crate::network::sonarr_network) async fn extract_and_add_sonarr_tag_ids_vec(
    &mut self,
    edit_tags: &str,
  ) -> Vec<i64> {
    let missing_tags_vec = {
      let tags_map = &self.app.lock().await.data.sonarr_data.tags_map;
      edit_tags
        .split(',')
        .filter(|&tag| {
          !tag.is_empty() && tags_map.get_by_right(tag.to_lowercase().trim()).is_none()
        })
        .collect::<Vec<&str>>()
    };

    for tag in missing_tags_vec {
      self
        .add_sonarr_tag(tag.trim().to_owned())
        .await
        .expect("Unable to add tag");
    }

    let app = self.app.lock().await;
    edit_tags
      .split(',')
      .filter(|tag| !tag.is_empty())
      .map(|tag| {
        *app
          .data
          .sonarr_data
          .tags_map
          .get_by_right(tag.to_lowercase().trim())
          .unwrap()
      })
      .collect()
  }
}
