use crate::models::servarr_data::modals::IndexerTestResultModalItem;
use crate::models::servarr_models::IndexerSettings;
use crate::models::servarr_models::{EditIndexerParams, Indexer, IndexerTestResult};
use crate::models::stateful_table::StatefulTable;
use crate::network::sonarr_network::SonarrEvent;
use crate::network::{Network, RequestMethod};
use anyhow::{Context, Result};
use log::{debug, info};
use serde_json::{Value, json};

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

impl Network<'_, '_> {
  pub(in crate::network::sonarr_network) async fn delete_sonarr_indexer(
    &mut self,
    indexer_id: i64,
  ) -> Result<()> {
    let event = SonarrEvent::DeleteIndexer(indexer_id);
    info!("Deleting Sonarr indexer for indexer with id: {indexer_id}");

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

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

  pub(in crate::network::sonarr_network) async fn edit_all_sonarr_indexer_settings(
    &mut self,
    params: IndexerSettings,
  ) -> Result<Value> {
    info!("Updating Sonarr indexer settings");
    let event = SonarrEvent::EditAllIndexerSettings(IndexerSettings::default());
    debug!("Indexer settings body: {params:?}");

    let request_props = self
      .request_props_from(event, RequestMethod::Put, Some(params), None, None)
      .await;

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

  pub(in crate::network::sonarr_network) async fn edit_sonarr_indexer(
    &mut self,
    mut edit_indexer_params: EditIndexerParams,
  ) -> Result<()> {
    if let Some(tag_input_str) = edit_indexer_params.tag_input_string.as_ref() {
      let tag_ids_vec = self.extract_and_add_sonarr_tag_ids_vec(tag_input_str).await;
      edit_indexer_params.tags = Some(tag_ids_vec);
    }
    let detail_event = SonarrEvent::GetIndexers;
    let event = SonarrEvent::EditIndexer(EditIndexerParams::default());
    let id = edit_indexer_params.indexer_id;
    info!("Updating Sonarr indexer with ID: {id}");
    info!("Fetching indexer details for indexer with ID: {id}");

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

    let mut response = String::new();

    self
      .handle_request::<(), Value>(request_props, |detailed_indexer_body, _| {
        response = detailed_indexer_body.to_string()
      })
      .await?;

    info!("Constructing edit indexer body");

    let mut detailed_indexer_body: Value = serde_json::from_str(&response)?;

    let (
      name,
      enable_rss,
      enable_automatic_search,
      enable_interactive_search,
      url,
      api_key,
      seed_ratio,
      tags,
      priority,
    ) = {
      let priority = detailed_indexer_body["priority"]
        .as_i64()
        .context("Failed to deserialize indexer 'priority' field")?;
      let seed_ratio_field_option = detailed_indexer_body["fields"]
        .as_array()
        .context("Failed to get indexer 'fields' array")?
        .iter()
        .find(|field| field["name"] == "seedCriteria.seedRatio");
      let name = edit_indexer_params.name.unwrap_or(
        detailed_indexer_body["name"]
          .as_str()
          .context("Failed to deserialize indexer 'name' field")?
          .to_owned(),
      );
      let enable_rss = edit_indexer_params.enable_rss.unwrap_or(
        detailed_indexer_body["enableRss"]
          .as_bool()
          .context("Failed to deserialize indexer 'enableRss' field")?,
      );
      let enable_automatic_search = edit_indexer_params.enable_automatic_search.unwrap_or(
        detailed_indexer_body["enableAutomaticSearch"]
          .as_bool()
          .context("Failed to deserialize indexer 'enableAutomaticSearch' field")?,
      );
      let enable_interactive_search = edit_indexer_params.enable_interactive_search.unwrap_or(
        detailed_indexer_body["enableInteractiveSearch"]
          .as_bool()
          .context("Failed to deserialize indexer 'enableInteractiveSearch' field")?,
      );
      let url = edit_indexer_params.url.unwrap_or(
        detailed_indexer_body["fields"]
          .as_array()
          .context("Failed to get indexer 'fields' array for baseUrl")?
          .iter()
          .find(|field| field["name"] == "baseUrl")
          .context("Field 'baseUrl' was not found in the indexer fields array")?
          .get("value")
          .unwrap_or(&json!(""))
          .as_str()
          .context("Failed to deserialize indexer 'baseUrl' value")?
          .to_owned(),
      );
      let api_key = edit_indexer_params.api_key.unwrap_or(
        detailed_indexer_body["fields"]
          .as_array()
          .context("Failed to get indexer 'fields' array for apiKey")?
          .iter()
          .find(|field| field["name"] == "apiKey")
          .context("Field 'apiKey' was not found in the indexer fields array")?
          .get("value")
          .unwrap_or(&json!(""))
          .as_str()
          .context("Failed to deserialize indexer 'apiKey' value")?
          .to_owned(),
      );
      let seed_ratio = edit_indexer_params.seed_ratio.unwrap_or_else(|| {
        if let Some(seed_ratio_field) = seed_ratio_field_option {
          return seed_ratio_field
            .get("value")
            .unwrap_or(&json!(""))
            .as_str()
            .unwrap_or("")
            .to_owned();
        }

        String::new()
      });
      let tags = if edit_indexer_params.clear_tags {
        vec![]
      } else {
        edit_indexer_params.tags.unwrap_or(
          detailed_indexer_body["tags"]
            .as_array()
            .context("Failed to get indexer 'tags' array")?
            .iter()
            .map(|item| {
              item
                .as_i64()
                .context("Failed to deserialize indexer tag ID")
            })
            .collect::<Result<Vec<_>>>()?,
        )
      };
      let priority = edit_indexer_params.priority.unwrap_or(priority);

      (
        name,
        enable_rss,
        enable_automatic_search,
        enable_interactive_search,
        url,
        api_key,
        seed_ratio,
        tags,
        priority,
      )
    };

    *detailed_indexer_body
      .get_mut("name")
      .context("Failed to get mutable reference to indexer 'name' field")? = json!(name);
    *detailed_indexer_body
      .get_mut("priority")
      .context("Failed to get mutable reference to indexer 'priority' field")? = json!(priority);
    *detailed_indexer_body
      .get_mut("enableRss")
      .context("Failed to get mutable reference to indexer 'enableRss' field")? = json!(enable_rss);
    *detailed_indexer_body
      .get_mut("enableAutomaticSearch")
      .context("Failed to get mutable reference to indexer 'enableAutomaticSearch' field")? =
      json!(enable_automatic_search);
    *detailed_indexer_body
      .get_mut("enableInteractiveSearch")
      .context("Failed to get mutable reference to indexer 'enableInteractiveSearch' field")? =
      json!(enable_interactive_search);
    *detailed_indexer_body
      .get_mut("fields")
      .and_then(|f| f.as_array_mut())
      .context("Failed to get mutable reference to indexer 'fields' array")?
      .iter_mut()
      .find(|field| field["name"] == "baseUrl")
      .context("Failed to find 'baseUrl' field in indexer fields array")?
      .get_mut("value")
      .context("Failed to get mutable reference to 'baseUrl' value")? = json!(url);
    *detailed_indexer_body
      .get_mut("fields")
      .and_then(|f| f.as_array_mut())
      .context("Failed to get mutable reference to indexer 'fields' array for apiKey")?
      .iter_mut()
      .find(|field| field["name"] == "apiKey")
      .context("Failed to find 'apiKey' field in indexer fields array")?
      .get_mut("value")
      .context("Failed to get mutable reference to 'apiKey' value")? = json!(api_key);
    *detailed_indexer_body
      .get_mut("tags")
      .context("Failed to get mutable reference to indexer 'tags' field")? = json!(tags);
    let seed_ratio_field_option = detailed_indexer_body
      .get_mut("fields")
      .and_then(|f| f.as_array_mut())
      .context("Failed to get mutable reference to indexer 'fields' array for seed ratio")?
      .iter_mut()
      .find(|field| field["name"] == "seedCriteria.seedRatio");
    if let Some(seed_ratio_field) = seed_ratio_field_option {
      seed_ratio_field
        .as_object_mut()
        .context("Failed to get mutable reference to 'seedCriteria.seedRatio' object")?
        .insert("value".to_string(), json!(seed_ratio));
    }

    debug!("Edit indexer body: {detailed_indexer_body:?}");

    let request_props = self
      .request_props_from(
        event,
        RequestMethod::Put,
        Some(detailed_indexer_body),
        Some(format!("/{id}")),
        Some("forceSave=true".to_owned()),
      )
      .await;

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

  pub(in crate::network::sonarr_network) async fn get_all_sonarr_indexer_settings(
    &mut self,
  ) -> Result<IndexerSettings> {
    info!("Fetching Sonarr indexer settings");
    let event = SonarrEvent::GetAllIndexerSettings;

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

    self
      .handle_request::<(), IndexerSettings>(request_props, |indexer_settings, mut app| {
        if app.data.sonarr_data.indexer_settings.is_none() {
          app.data.sonarr_data.indexer_settings = Some(indexer_settings);
        } else {
          debug!("Indexer Settings are being modified. Ignoring update...");
        }
      })
      .await
  }

  pub(in crate::network::sonarr_network) async fn get_sonarr_indexers(
    &mut self,
  ) -> Result<Vec<Indexer>> {
    info!("Fetching Sonarr indexers");
    let event = SonarrEvent::GetIndexers;

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

    self
      .handle_request::<(), Vec<Indexer>>(request_props, |indexers, mut app| {
        app.data.sonarr_data.indexers.set_items(indexers);
      })
      .await
  }

  pub(in crate::network::sonarr_network) async fn test_sonarr_indexer(
    &mut self,
    indexer_id: i64,
  ) -> Result<Value> {
    let detail_event = SonarrEvent::GetIndexers;
    let event = SonarrEvent::TestIndexer(indexer_id);
    info!("Testing Sonarr indexer with ID: {indexer_id}");

    info!("Fetching indexer details for indexer with ID: {indexer_id}");

    let request_props = self
      .request_props_from(
        detail_event,
        RequestMethod::Get,
        None::<()>,
        Some(format!("/{indexer_id}")),
        None,
      )
      .await;

    let mut test_body: Value = Value::default();

    self
      .handle_request::<(), Value>(request_props, |detailed_indexer_body, _| {
        test_body = detailed_indexer_body;
      })
      .await?;

    info!("Testing indexer");

    let mut request_props = self
      .request_props_from(event, RequestMethod::Post, Some(test_body), None, None)
      .await;
    request_props.ignore_status_code = true;

    self
      .handle_request::<Value, Value>(request_props, |test_results, mut app| {
        if test_results.as_object().is_none() {
          let error_message = test_results
            .as_array()
            .and_then(|arr| arr.first())
            .and_then(|item| item.get("errorMessage"))
            .map(|msg| msg.to_string())
            .unwrap_or_else(|| "Unknown indexer test error".to_string());
          app.data.sonarr_data.indexer_test_errors = Some(error_message);
        } else {
          app.data.sonarr_data.indexer_test_errors = Some(String::new());
        };
      })
      .await
  }

  pub(in crate::network::sonarr_network) async fn test_all_sonarr_indexers(
    &mut self,
  ) -> Result<Vec<IndexerTestResult>> {
    info!("Testing all Sonarr indexers");
    let event = SonarrEvent::TestAllIndexers;

    let mut request_props = self
      .request_props_from(event, RequestMethod::Post, None, None, None)
      .await;
    request_props.ignore_status_code = true;

    let result = self
      .handle_request::<(), Vec<IndexerTestResult>>(request_props, |test_results, mut app| {
        let mut test_all_indexer_results = StatefulTable::default();
        let indexers = app.data.sonarr_data.indexers.items.clone();
        let modal_test_results = test_results
          .iter()
          .map(|result| {
            let name = indexers
              .iter()
              .filter(|&indexer| indexer.id == result.id)
              .map(|indexer| indexer.name.clone())
              .nth(0)
              .unwrap_or_default();
            let validation_failures = result
              .validation_failures
              .iter()
              .map(|failure| {
                format!(
                  "Failure for field '{}': {}",
                  failure.property_name, failure.error_message
                )
              })
              .collect::<Vec<String>>()
              .join(", ");

            IndexerTestResultModalItem {
              name: name.unwrap_or_default(),
              is_valid: result.is_valid,
              validation_failures: validation_failures.into(),
            }
          })
          .collect();
        test_all_indexer_results.set_items(modal_test_results);
        app.data.sonarr_data.indexer_test_all_results = Some(test_all_indexer_results);
      })
      .await;

    if result.is_err() {
      self
        .app
        .lock()
        .await
        .data
        .sonarr_data
        .indexer_test_all_results = Some(StatefulTable::default());
    }

    result
  }
}
