use crate::app::App;
use crate::models::Route;
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SEASON_DETAILS_BLOCKS};
use crate::models::sonarr_models::{
  DownloadRecord, DownloadStatus, Episode, SonarrHistoryItem, SonarrRelease,
};
use crate::ui::sonarr_ui::library::episode_details_ui::EpisodeDetailsUi;
use crate::ui::sonarr_ui::sonarr_ui_utils::create_history_event_details;
use crate::ui::styles::ManagarrStyle;
use crate::ui::styles::secondary_style;
use crate::ui::utils::{
  borderless_block, decorate_peer_style, get_width_from_percentage, layout_block_top_border,
};
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
use crate::ui::widgets::loading_block::LoadingBlock;
use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::widgets::message::Message;
use crate::ui::widgets::popup::{Popup, Size};
use crate::ui::{DrawUi, draw_popup, draw_tabs};
use crate::utils::convert_to_gb;
use chrono::Utc;
use ratatui::Frame;
use ratatui::layout::{Alignment, Constraint, Rect};
use ratatui::prelude::{Line, Stylize, Text};
use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
use serde_json::Number;

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

pub(super) struct SeasonDetailsUi;

impl DrawUi for SeasonDetailsUi {
  fn accepts(route: Route) -> bool {
    let Route::Sonarr(active_sonarr_block, _) = route else {
      return false;
    };
    EpisodeDetailsUi::accepts(route) || SEASON_DETAILS_BLOCKS.contains(&active_sonarr_block)
  }

  fn draw(f: &mut Frame<'_>, app: &mut App<'_>, _area: Rect) {
    let route = app.get_current_route();
    if app.data.sonarr_data.season_details_modal.is_some()
      && let Route::Sonarr(active_sonarr_block, _) = app.get_current_route()
    {
      let draw_season_details_popup = |f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect| {
        let content_area = draw_tabs(
          f,
          popup_area,
          &format!(
            "Season {} Details",
            app
              .data
              .sonarr_data
              .seasons
              .current_selection()
              .season_number
          ),
          &app
            .data
            .sonarr_data
            .season_details_modal
            .as_ref()
            .expect("season_details_modal must exist in this context")
            .season_details_tabs,
        );
        draw_season_details(f, app, content_area);

        match active_sonarr_block {
          ActiveSonarrBlock::AutomaticallySearchSeasonPrompt => {
            let prompt = format!(
              "Do you want to trigger an automatic search of your indexers for season packs for: {}",
              app
                .data
                .sonarr_data
                .seasons
                .current_selection()
                .title
                .as_ref()
                .unwrap()
            );
            let confirmation_prompt = ConfirmationPrompt::new()
              .title("Automatic Season Search")
              .prompt(&prompt)
              .yes_no_value(app.data.sonarr_data.prompt_confirm);

            f.render_widget(
              Popup::new(confirmation_prompt).size(Size::MediumPrompt),
              f.area(),
            );
          }
          ActiveSonarrBlock::DeleteEpisodeFilePrompt => {
            let prompt = format!(
              "Do you really want to delete this episode: \n{}?",
              app
                .data
                .sonarr_data
                .season_details_modal
                .as_ref()
                .expect("season_details_modal must exist in this context")
                .episodes
                .current_selection()
                .title
            );
            let confirmation_prompt = ConfirmationPrompt::new()
              .title("Delete Episode")
              .prompt(&prompt)
              .yes_no_value(app.data.sonarr_data.prompt_confirm);

            f.render_widget(
              Popup::new(confirmation_prompt).size(Size::MediumPrompt),
              f.area(),
            );
          }
          ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt => {
            draw_manual_season_search_confirm_prompt(f, app);
          }
          ActiveSonarrBlock::SeasonHistoryDetails => {
            draw_history_item_details_popup(f, app);
          }
          _ => (),
        }
      };

      draw_popup(f, app, draw_season_details_popup, Size::XLarge);

      if EpisodeDetailsUi::accepts(route) {
        EpisodeDetailsUi::draw(f, app, _area);
      }
    }
  }
}

pub fn draw_season_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
  if let Some(season_details_modal) = app.data.sonarr_data.season_details_modal.as_ref()
    && let Route::Sonarr(active_sonarr_block, _) =
      season_details_modal.season_details_tabs.get_active_route()
  {
    match active_sonarr_block {
      ActiveSonarrBlock::SeasonDetails => draw_episodes_table(f, app, area),
      ActiveSonarrBlock::SeasonHistory => draw_season_history_table(f, app, area),
      ActiveSonarrBlock::ManualSeasonSearch => draw_season_releases(f, app, area),
      _ => (),
    }
  }
}

fn draw_episodes_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
  if let Route::Sonarr(active_sonarr_block, _) = app.get_current_route() {
    let episode_files = app
      .data
      .sonarr_data
      .season_details_modal
      .as_ref()
      .expect("Season details modal is unpopulated")
      .episode_files
      .items
      .clone();
    let content = Some(
      &mut app
        .data
        .sonarr_data
        .season_details_modal
        .as_mut()
        .expect("Season details modal is unpopulated")
        .episodes,
    );
    let downloads_vec = &app.data.sonarr_data.downloads.items;

    let episode_row_mapping = |episode: &Episode| {
      let Episode {
        episode_number,
        title,
        air_date_utc,
        episode_file_id,
        ..
      } = episode;
      let episode_file = episode_files
        .iter()
        .find(|episode_file| episode_file.id == *episode_file_id);
      let (quality_profile, size_on_disk) = if let Some(episode_file) = episode_file {
        (
          episode_file.quality.quality.name.to_owned(),
          episode_file.size,
        )
      } else {
        (String::new(), 0)
      };

      let episode_monitored = if episode.monitored { "🏷" } else { "" };
      let size = convert_to_gb(size_on_disk);
      let air_date = if let Some(air_date) = air_date_utc.as_ref() {
        air_date.to_string()
      } else {
        String::new()
      };

      decorate_with_row_style(
        downloads_vec,
        episode,
        Row::new(vec![
          Cell::from(episode_monitored.to_owned()),
          Cell::from(episode_number.to_string()),
          Cell::from(title.clone()),
          Cell::from(air_date),
          Cell::from(format!("{size:.2} GB")),
          Cell::from(quality_profile),
        ]),
      )
    };
    let is_searching = active_sonarr_block == ActiveSonarrBlock::SearchEpisodes;
    let season_table = ManagarrTable::new(content, episode_row_mapping)
      .block(layout_block_top_border())
      .loading(app.is_loading)
      .searching(is_searching)
      .search_produced_empty_results(active_sonarr_block == ActiveSonarrBlock::SearchEpisodesError)
      .headers([
        "🏷",
        "#",
        "Title",
        "Air Date",
        "Size on Disk",
        "Quality Profile",
      ])
      .constraints([
        Constraint::Percentage(4),
        Constraint::Percentage(4),
        Constraint::Percentage(50),
        Constraint::Percentage(19),
        Constraint::Percentage(10),
        Constraint::Percentage(12),
      ]);

    if is_searching {
      season_table.show_cursor(f, area);
    }

    f.render_widget(season_table, area);
  }
}

fn draw_season_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
  match app.data.sonarr_data.season_details_modal.as_ref() {
    Some(season_details_modal) if !app.is_loading => {
      let current_selection = if season_details_modal.season_history.is_empty() {
        SonarrHistoryItem::default()
      } else {
        season_details_modal
          .season_history
          .current_selection()
          .clone()
      };

      if let Route::Sonarr(active_sonarr_block, _) = app.get_current_route() {
        let history_row_mapping = |history_item: &SonarrHistoryItem| {
          let SonarrHistoryItem {
            source_title,
            languages,
            quality,
            event_type,
            date,
            ..
          } = history_item;

          source_title.scroll_left_or_reset(
            get_width_from_percentage(area, 40),
            current_selection == *history_item,
            app.ui_scroll_tick_count == 0,
          );

          Row::new(vec![
            Cell::from(source_title.to_string()),
            Cell::from(event_type.to_string()),
            Cell::from(
              languages
                .iter()
                .map(|language| {
                  language
                    .as_ref()
                    .unwrap_or(&Default::default())
                    .name
                    .to_owned()
                })
                .collect::<Vec<String>>()
                .join(","),
            ),
            Cell::from(quality.quality.name.to_owned()),
            Cell::from(date.to_string()),
          ])
          .primary()
        };
        let mut season_history_table = &mut app
          .data
          .sonarr_data
          .season_details_modal
          .as_mut()
          .expect("season_details_modal must exist in this context")
          .season_history;
        let history_table =
          ManagarrTable::new(Some(&mut season_history_table), history_row_mapping)
            .block(layout_block_top_border())
            .loading(app.is_loading)
            .sorting(active_sonarr_block == ActiveSonarrBlock::SeasonHistorySortPrompt)
            .searching(active_sonarr_block == ActiveSonarrBlock::SearchSeasonHistory)
            .search_produced_empty_results(
              active_sonarr_block == ActiveSonarrBlock::SearchSeasonHistoryError,
            )
            .filtering(active_sonarr_block == ActiveSonarrBlock::FilterSeasonHistory)
            .filter_produced_empty_results(
              active_sonarr_block == ActiveSonarrBlock::FilterSeasonHistoryError,
            )
            .headers(["Source Title", "Event Type", "Language", "Quality", "Date"])
            .constraints([
              Constraint::Percentage(40),
              Constraint::Percentage(15),
              Constraint::Percentage(12),
              Constraint::Percentage(13),
              Constraint::Percentage(20),
            ]);

        if [
          ActiveSonarrBlock::SearchSeriesHistory,
          ActiveSonarrBlock::FilterSeriesHistory,
        ]
        .contains(&active_sonarr_block)
        {
          history_table.show_cursor(f, area);
        }

        f.render_widget(history_table, area);
      }
    }
    _ => f.render_widget(
      LoadingBlock::new(
        app.is_loading || app.data.sonarr_data.season_details_modal.is_none(),
        layout_block_top_border(),
      ),
      area,
    ),
  }
}

fn draw_season_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
  match app.data.sonarr_data.season_details_modal.as_ref() {
    Some(season_details_modal) if !app.is_loading => {
      let (current_selection, is_empty) = if season_details_modal.season_releases.is_empty() {
        (SonarrRelease::default(), true)
      } else {
        (
          season_details_modal
            .season_releases
            .current_selection()
            .clone(),
          season_details_modal.season_releases.is_empty(),
        )
      };

      if let Route::Sonarr(active_sonarr_block, _) = app.get_current_route() {
        let season_release_row_mapping = |release: &SonarrRelease| {
          let SonarrRelease {
            protocol,
            age,
            title,
            indexer,
            size,
            rejected,
            seeders,
            leechers,
            languages,
            quality,
            ..
          } = release;

          let age = format!("{age} days");
          title.scroll_left_or_reset(
            get_width_from_percentage(area, 30),
            current_selection == *release
              && active_sonarr_block != ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt,
            app.ui_scroll_tick_count == 0,
          );
          let size = convert_to_gb(*size);
          let rejected_str = if *rejected { "⛔" } else { "" };
          let peers = if seeders.is_none() || leechers.is_none() {
            Text::from("")
          } else {
            let seeders = seeders
              .clone()
              .unwrap_or(Number::from(0u64))
              .as_u64()
              .unwrap_or_default();
            let leechers = leechers
              .clone()
              .unwrap_or(Number::from(0u64))
              .as_u64()
              .unwrap_or_default();

            decorate_peer_style(
              seeders,
              leechers,
              Text::from(format!("{seeders} / {leechers}")),
            )
          };

          let language = if languages.is_some() {
            languages.clone().unwrap()[0]
              .as_ref()
              .unwrap_or(&Default::default())
              .name
              .clone()
          } else {
            String::new()
          };
          let quality = quality.quality.name.clone();

          Row::new(vec![
            Cell::from(protocol.clone()),
            Cell::from(age),
            Cell::from(rejected_str),
            Cell::from(title.to_string()),
            Cell::from(indexer.clone()),
            Cell::from(format!("{size:.1} GB")),
            Cell::from(peers),
            Cell::from(language),
            Cell::from(quality),
          ])
          .primary()
        };
        let mut season_release_table = &mut app
          .data
          .sonarr_data
          .season_details_modal
          .as_mut()
          .expect("season_details_modal must exist in this context")
          .season_releases;
        let release_table =
          ManagarrTable::new(Some(&mut season_release_table), season_release_row_mapping)
            .block(layout_block_top_border())
            .loading(app.is_loading || is_empty)
            .sorting(active_sonarr_block == ActiveSonarrBlock::ManualSeasonSearchSortPrompt)
            .headers([
              "Source", "Age", "⛔", "Title", "Indexer", "Size", "Peers", "Language", "Quality",
            ])
            .constraints([
              Constraint::Length(9),
              Constraint::Length(10),
              Constraint::Length(5),
              Constraint::Percentage(30),
              Constraint::Percentage(18),
              Constraint::Length(12),
              Constraint::Length(12),
              Constraint::Percentage(7),
              Constraint::Percentage(10),
            ]);

        f.render_widget(release_table, area);
      }
    }
    _ => f.render_widget(
      LoadingBlock::new(
        app.is_loading || app.data.sonarr_data.season_details_modal.is_none(),
        layout_block_top_border(),
      ),
      area,
    ),
  }
}

fn draw_manual_season_search_confirm_prompt(f: &mut Frame<'_>, app: &mut App<'_>) {
  let current_selection = app
    .data
    .sonarr_data
    .season_details_modal
    .as_ref()
    .expect("season_details_modal must exist in this context")
    .season_releases
    .current_selection();
  let title = if current_selection.rejected {
    "Download Rejected Release"
  } else {
    "Download Release"
  };
  let prompt = if current_selection.rejected {
    format!(
      "Do you really want to download the rejected release: {}?",
      &current_selection.title.text
    )
  } else {
    format!(
      "Do you want to download the release: {}?",
      &current_selection.title.text
    )
  };

  if current_selection.rejected {
    let mut lines_vec = vec![Line::from("Rejection reasons: ".primary().bold())];
    let mut rejections_spans = current_selection
      .rejections
      .clone()
      .unwrap_or_default()
      .iter()
      .map(|item| Line::from(format!("• {item}").primary().bold()))
      .collect::<Vec<Line<'_>>>();
    lines_vec.append(&mut rejections_spans);

    let content_paragraph = Paragraph::new(lines_vec)
      .block(borderless_block())
      .wrap(Wrap { trim: false })
      .left_aligned();
    let confirmation_prompt = ConfirmationPrompt::new()
      .title(title)
      .prompt(&prompt)
      .content(content_paragraph)
      .yes_no_value(app.data.sonarr_data.prompt_confirm);

    f.render_widget(Popup::new(confirmation_prompt).size(Size::Small), f.area());
  } else {
    let confirmation_prompt = ConfirmationPrompt::new()
      .title(title)
      .prompt(&prompt)
      .yes_no_value(app.data.sonarr_data.prompt_confirm);

    f.render_widget(
      Popup::new(confirmation_prompt).size(Size::MediumPrompt),
      f.area(),
    );
  }
}

fn draw_history_item_details_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
  let current_selection =
    if let Some(season_details_modal) = app.data.sonarr_data.season_details_modal.as_ref() {
      if season_details_modal.season_history.is_empty() {
        SonarrHistoryItem::default()
      } else {
        season_details_modal
          .season_history
          .current_selection()
          .clone()
      }
    } else {
      SonarrHistoryItem::default()
    };

  let line_vec = create_history_event_details(current_selection);
  let text = Text::from(line_vec);

  let message = Message::new(text)
    .title("Details")
    .style(secondary_style())
    .alignment(Alignment::Left);

  f.render_widget(Popup::new(message).size(Size::NarrowLongMessage), f.area());
}

fn decorate_with_row_style<'a>(
  downloads_vec: &[DownloadRecord],
  episode: &Episode,
  row: Row<'a>,
) -> Row<'a> {
  if !episode.has_file {
    let default_episode_id = Number::from(-1i64);
    if let Some(download) = downloads_vec.iter().find(|&download| {
      download
        .episode_id
        .as_ref()
        .unwrap_or(&default_episode_id)
        .as_i64()
        .unwrap()
        == episode.id
    }) {
      if download.status == DownloadStatus::Downloading {
        return row.downloading();
      }

      if download.status == DownloadStatus::Completed {
        return row.awaiting_import();
      }
    }

    if !episode.monitored {
      return row.unmonitored_missing();
    }

    if let Some(air_date) = episode.air_date_utc.as_ref()
      && air_date > &Utc::now()
    {
      return row.unreleased();
    }

    return row.missing();
  }

  if !episode.monitored {
    row.unmonitored()
  } else {
    row.downloaded()
  }
}
