use super::*;
use crate::action::Action;
use crate::audio::AudioCommand;
use crate::system_open;
use crate::system_trash;
use crate::tape_archive::{TapeArchive, TapeArchiveRow, TapeArchiveStatus, TapeTrack};
use std::path::{Path, PathBuf};
use std::time::Duration;

const TAPE_ARCHIVE_PAGE: usize = 1;

impl App {
    pub fn is_tape_archive_page(&self) -> bool {
        !self.show_help && !self.show_settings && self.active_deck_page == TAPE_ARCHIVE_PAGE
    }

    pub fn is_tape_archive_focused(&self) -> bool {
        self.input_mode == InputMode::Normal && self.is_tape_archive_page()
    }

    pub fn take_tape_archive_scan_request(&mut self) -> Option<PathBuf> {
        if !self.tape_archive_scan_requested || self.tape_archive_scan_inflight {
            return None;
        }

        let root = self.current_tape_archive_root();
        self.tape_archive_scan_requested = false;
        self.tape_archive_scan_inflight = true;
        self.tape_archive.root = root.clone();
        self.tape_archive.status = TapeArchiveStatus::Scanning;
        Some(root)
    }

    pub fn apply_tape_archive_scan(&mut self, root: PathBuf, result: Result<TapeArchive, String>) {
        if root != self.current_tape_archive_root() {
            self.tape_archive_scan_inflight = false;
            self.tape_archive_scan_requested = true;
            return;
        }

        self.tape_archive_scan_inflight = false;
        let previous_selected = self.tape_archive.selected;

        match result {
            Ok(mut archive) => {
                archive.selected = previous_selected.min(archive.row_count().saturating_sub(1));
                self.tape_archive = archive;
            }
            Err(err) => {
                self.tape_archive.status = TapeArchiveStatus::Error(err);
                self.tape_archive.rebuild_flattened();
            }
        }
    }

    pub(super) fn request_tape_archive_scan_if_needed(&mut self) {
        let root = self.current_tape_archive_root();
        if self.tape_archive.root != root
            || matches!(
                self.tape_archive.status,
                TapeArchiveStatus::NotLoaded | TapeArchiveStatus::Error(_)
            )
        {
            self.tape_archive_scan_requested = true;
        }
    }

    pub(super) fn cycle_tape_playback_mode(&mut self) {
        self.tape_playback_mode = self.tape_playback_mode.next();
        self.set_info_notice(format!(
            "Tape playback mode: {} — {}",
            self.tape_playback_mode.label(),
            self.tape_playback_mode.description()
        ));
    }

    pub(super) fn handle_local_tape_finished(&mut self, path: PathBuf) {
        self.local_playback_started_at = None;
        self.local_playback_elapsed_before_pause = Duration::ZERO;

        let next = match self.tape_playback_mode {
            TapePlaybackMode::StopAtEnd => None,
            TapePlaybackMode::Folder => self.tape_archive.next_track_after(&path),
            TapePlaybackMode::AllRecordings => self.tape_archive.next_track_after_any_folder(&path),
            TapePlaybackMode::RepeatOne => self.tape_archive.track_by_path(&path),
            TapePlaybackMode::ShuffleAll => self
                .tape_archive
                .deterministic_shuffle_track_after(&path, self.tick_count),
        };

        if let Some(next) = next {
            let title = next.title.clone();
            let mode = self.tape_playback_mode.label();
            self.play_tape_track(next);
            self.set_info_notice(format!("Tape {mode}: {title}"));
        } else {
            self.local_playback_path = None;
            self.local_playback_started_at = None;
            self.local_playback_elapsed_before_pause = Duration::ZERO;
            self.current_track = None;
            self.playback = PlaybackState::Stopped;
            self.set_info_notice(match self.tape_playback_mode {
                TapePlaybackMode::StopAtEnd => "Tape playback stopped at end",
                TapePlaybackMode::Folder => "End of tape folder",
                TapePlaybackMode::AllRecordings => "End of all recordings",
                TapePlaybackMode::RepeatOne => "Tape repeat ended",
                TapePlaybackMode::ShuffleAll => "No other tapes to shuffle",
            });
        }
    }

    pub(super) fn toggle_tape_details(&mut self) {
        if !self.is_tape_archive_page() {
            return;
        }

        if self.tape_archive.selected_track().is_none() {
            self.set_info_notice("Select a tape file to inspect details");
            return;
        }

        self.tape_details_visible = !self.tape_details_visible;
        self.set_info_notice(if self.tape_details_visible {
            "Tape details visible"
        } else {
            "Tape details hidden"
        });
    }

    pub(super) fn enter_tape_rename(&mut self) {
        if !self.is_tape_archive_focused() {
            return;
        }

        let Some(track) = self.tape_archive.selected_track() else {
            self.set_info_notice("Select a tape file to rename");
            return;
        };

        self.pending_tape_delete = None;
        self.tape_edit_buffer = track
            .path
            .file_stem()
            .and_then(|stem| stem.to_str())
            .unwrap_or(track.title.as_str())
            .to_string();
        self.input_mode = InputMode::TapeRename;
        self.set_info_notice("Rename tape: edit name and press Enter");
    }

    pub(super) fn enter_tape_move(&mut self) {
        if !self.is_tape_archive_focused() {
            return;
        }

        if self.tape_archive.selected_track().is_none() {
            self.set_info_notice("Select a tape file to move");
            return;
        }

        self.pending_tape_delete = None;
        self.tape_edit_buffer = self
            .tape_archive
            .selected_track_folder_name()
            .unwrap_or("")
            .to_string();
        self.input_mode = InputMode::TapeMove;
        self.set_info_notice("Move tape: type target folder and press Enter");
    }

    pub(super) fn handle_tape_manager_action(&mut self, action: Action) {
        match action {
            Action::TapeManagerInput(ch) => self.tape_manager_input(ch),
            Action::TapeManagerBackspace => self.tape_manager_backspace(),
            Action::ConfirmTapeRename => self.confirm_tape_rename(),
            Action::ConfirmTapeMove => self.confirm_tape_move(),
            Action::CancelTapeManager => self.cancel_tape_manager(),
            Action::Tick => self.tick(),
            Action::Quit => self.quit(),
            _ => {}
        }
    }

    pub(super) fn tape_manager_input(&mut self, ch: char) {
        if matches!(self.input_mode, InputMode::TapeRename | InputMode::TapeMove)
            && !ch.is_control()
        {
            self.tape_edit_buffer.push(ch);
        }
    }

    pub(super) fn tape_manager_backspace(&mut self) {
        if matches!(self.input_mode, InputMode::TapeRename | InputMode::TapeMove) {
            self.tape_edit_buffer.pop();
        }
    }

    pub(super) fn cancel_tape_manager(&mut self) {
        if matches!(self.input_mode, InputMode::TapeRename | InputMode::TapeMove) {
            self.input_mode = InputMode::Normal;
            self.tape_edit_buffer.clear();
            self.set_info_notice("Tape file edit cancelled");
        }
    }

    pub(super) fn confirm_tape_rename(&mut self) {
        if self.input_mode != InputMode::TapeRename {
            return;
        }

        let Some(track) = self.tape_archive.selected_track().cloned() else {
            self.cancel_tape_manager();
            return;
        };

        let Ok(new_stem) = sanitized_tape_component(self.tape_edit_buffer.as_str()) else {
            self.set_error_notice("Tape name cannot be empty");
            return;
        };

        let extension = track
            .path
            .extension()
            .and_then(|ext| ext.to_str())
            .unwrap_or(track.extension.as_str());

        let filename = if extension.trim().is_empty() {
            new_stem
        } else {
            format!("{new_stem}.{extension}")
        };

        let Some(parent) = track.path.parent() else {
            self.set_error_notice("Selected tape has no containing folder");
            return;
        };

        let target = parent.join(filename);
        self.apply_tape_file_move(track.path.as_path(), target.as_path(), "Tape renamed");
    }

    pub(super) fn confirm_tape_move(&mut self) {
        if self.input_mode != InputMode::TapeMove {
            return;
        }

        let Some(track) = self.tape_archive.selected_track().cloned() else {
            self.cancel_tape_manager();
            return;
        };

        let Ok(folder_name) = sanitized_tape_component(self.tape_edit_buffer.as_str()) else {
            self.set_error_notice("Target folder cannot be empty");
            return;
        };

        let target_dir = self.current_tape_archive_root().join(folder_name);
        let target = target_dir.join(track.filename.as_str());
        self.apply_tape_file_move(track.path.as_path(), target.as_path(), "Tape moved");
    }

    fn apply_tape_file_move(&mut self, source: &Path, target: &Path, success_notice: &'static str) {
        if source == target {
            self.input_mode = InputMode::Normal;
            self.tape_edit_buffer.clear();
            self.set_info_notice("No tape file change needed");
            return;
        }

        if target.exists() {
            self.set_error_notice(format!("Target already exists: {}", target.display()));
            return;
        }

        if let Some(parent) = target.parent() {
            if let Err(err) = std::fs::create_dir_all(parent) {
                self.set_error_notice(format!("Could not create target folder: {err}"));
                return;
            }
        }

        if self.local_playback_path.as_deref() == Some(source) {
            self.audio.send(AudioCommand::Stop);
            self.local_playback_path = None;
            self.local_playback_started_at = None;
            self.local_playback_elapsed_before_pause = Duration::ZERO;
            self.current_track = None;
            self.playback = PlaybackState::Stopped;
        }

        match std::fs::rename(source, target) {
            Ok(()) => {
                self.input_mode = InputMode::Normal;
                self.tape_edit_buffer.clear();
                self.pending_tape_delete = None;
                self.tape_archive_scan_requested = true;
                self.set_info_notice(success_notice);
            }
            Err(err) => {
                self.set_error_notice(format!("Could not update tape file: {err}"));
            }
        }
    }

    pub fn local_tape_elapsed(&self) -> Duration {
        let mut elapsed = self.local_playback_elapsed_before_pause;

        if self.local_playback_path.is_some() {
            if let Some(started_at) = self.local_playback_started_at {
                elapsed += started_at.elapsed().unwrap_or_default();
            }
        }

        elapsed
    }

    pub(super) fn refresh_tape_archive(&mut self) {
        if self.is_tape_archive_focused() {
            self.pending_tape_delete = None;
            self.tape_archive_scan_requested = true;
            self.set_info_notice("Refreshing Local Tape Library");
        }
    }

    pub(super) fn enter_tape_filter(&mut self) {
        if self.is_tape_archive_page() {
            self.pending_tape_delete = None;
            self.input_mode = InputMode::TapeFilter;
            self.set_info_notice("Filtering Local Tape Library");
        }
    }

    pub(super) fn exit_tape_filter(&mut self) {
        if self.input_mode == InputMode::TapeFilter {
            self.input_mode = InputMode::Normal;
            self.tape_archive.clear_filter_query();
            self.set_info_notice("Tape filter cleared");
        }
    }

    pub(super) fn tape_filter_input(&mut self, ch: char) {
        if self.input_mode == InputMode::TapeFilter && !ch.is_control() {
            self.tape_archive.push_filter_char(ch);
        }
    }

    pub(super) fn tape_filter_backspace(&mut self) {
        if self.input_mode == InputMode::TapeFilter {
            self.tape_archive.pop_filter_char();
        }
    }

    pub(super) fn handle_tape_filter_action(&mut self, action: Action) {
        match action {
            Action::TapeFilterInput(ch) => self.tape_filter_input(ch),
            Action::TapeFilterBackspace => self.tape_filter_backspace(),
            Action::ExitTapeFilter => self.exit_tape_filter(),
            Action::NextStation => self.next_tape_archive_row(),
            Action::PrevStation => self.prev_tape_archive_row(),
            Action::PlaySelected => self.play_selected_tape_or_toggle(),
            Action::RefreshTapeArchive => {
                self.pending_tape_delete = None;
                self.tape_archive_scan_requested = true;
                self.set_info_notice("Refreshing Local Tape Library");
            }
            Action::Tick => self.tick(),
            Action::Quit => self.quit(),
            _ => {}
        }
    }

    pub(super) fn next_tape_archive_row(&mut self) {
        self.pending_tape_delete = None;
        self.tape_archive.next_row();
    }

    pub(super) fn prev_tape_archive_row(&mut self) {
        self.pending_tape_delete = None;
        self.tape_archive.prev_row();
    }

    pub(super) fn play_selected_tape_or_toggle(&mut self) {
        if self.pending_tape_delete.is_some() {
            self.confirm_tape_delete();
            return;
        }

        match self.tape_archive.selected_row().cloned() {
            Some(TapeArchiveRow::Track { .. }) | Some(TapeArchiveRow::AllRecordingTrack { .. }) => {
                self.play_selected_tape()
            }
            Some(TapeArchiveRow::Folder { .. }) | Some(TapeArchiveRow::AllRecordings) => {
                self.tape_archive.toggle_selected_folder();
            }
            None => {}
        }
    }

    pub(super) fn toggle_tape_archive_folder_or_pause(&mut self) {
        match self.tape_archive.selected_row().cloned() {
            Some(TapeArchiveRow::Folder { .. }) | Some(TapeArchiveRow::AllRecordings) => {
                self.pending_tape_delete = None;
                self.tape_archive.toggle_selected_folder();
            }
            Some(TapeArchiveRow::Track { .. }) | Some(TapeArchiveRow::AllRecordingTrack { .. }) => {
                match self.playback {
                    PlaybackState::Playing
                    | PlaybackState::Paused
                    | PlaybackState::FadingOut { .. } => {
                        self.toggle_pause();
                    }
                    PlaybackState::Stopped
                    | PlaybackState::Error(_)
                    | PlaybackState::Connecting => {
                        self.play_selected_tape();
                    }
                }
            }
            None => {}
        }
    }

    pub(super) fn open_selected_tape_folder(&mut self) {
        if !self.is_tape_archive_page() {
            return;
        }

        let Some(track) = self.tape_archive.selected_track() else {
            self.set_info_notice("Select a tape file to open its folder");
            return;
        };

        match system_open::open_containing_folder(&track.path) {
            Ok(()) => self.set_info_notice("Opened tape folder"),
            Err(err) => self.set_error_notice(format!("Could not open tape folder: {err}")),
        }
    }

    pub(super) fn request_delete_selected_tape(&mut self) {
        if !self.is_tape_archive_focused() {
            return;
        }

        let Some(track) = self.tape_archive.selected_track() else {
            self.set_info_notice("Select a tape file to delete");
            return;
        };

        self.pending_tape_delete = Some(track.path.clone());
        self.set_info_notice(format!(
            "Delete {}? Press y to confirm, n to cancel",
            track.filename
        ));
    }

    pub(super) fn confirm_tape_delete(&mut self) {
        let Some(path) = self.pending_tape_delete.take() else {
            return;
        };

        if self.local_playback_path.as_ref() == Some(&path) {
            self.audio.send(AudioCommand::Stop);
            self.local_playback_path = None;
            self.local_playback_started_at = None;
            self.local_playback_elapsed_before_pause = Duration::ZERO;
            self.current_track = None;
            self.playback = PlaybackState::Stopped;
        }

        match system_trash::move_to_trash(&path) {
            Ok(()) => {
                self.set_info_notice("Tape moved to trash");
                self.tape_archive_scan_requested = true;
            }
            Err(err) => {
                self.set_error_notice(format!("Could not move tape to trash: {err}"));
            }
        }
    }

    pub(super) fn cancel_tape_delete(&mut self) {
        if self.pending_tape_delete.take().is_some() {
            self.set_info_notice("Tape delete cancelled");
        }
    }

    #[cfg(test)]
    pub(super) fn tape_archive_selected_track_path(&self) -> Option<PathBuf> {
        self.tape_archive
            .selected_track()
            .map(|track| track.path.clone())
    }

    fn play_selected_tape(&mut self) {
        let Some(track) = self.tape_archive.selected_track().cloned() else {
            return;
        };

        self.play_tape_track(track);
    }

    pub(super) fn play_tape_track(&mut self, track: TapeTrack) {
        self.playing_url = None;
        self.local_playback_path = Some(track.path.clone());
        self.local_playback_started_at = None;
        self.local_playback_elapsed_before_pause = Duration::ZERO;
        self.current_track = Some(track.title.clone());
        self.playback = PlaybackState::Connecting;
        self.buffer_percent = 0;
        self.buffer_seconds = 0;
        self.pending_tape_delete = None;

        self.audio.send(AudioCommand::PlayLocalFile(track.path));
        self.sync_volume();
    }

    fn current_tape_archive_root(&self) -> PathBuf {
        PathBuf::from(self.library.settings.recording_dir.clone())
    }
}

fn sanitized_tape_component(value: &str) -> Result<String, ()> {
    let cleaned = value
        .chars()
        .map(|ch| match ch {
            '\\' | '/' | ':' | '*' | '?' | '"' | '<' | '>' | '|' => '-',
            other => other,
        })
        .collect::<String>()
        .trim()
        .trim_matches('.')
        .to_string();

    if cleaned.is_empty() {
        Err(())
    } else {
        Ok(cleaned)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::favorites::Library;
    use crate::tape_archive::{TapeFolder, TapeTrack};
    use std::time::SystemTime;

    fn test_track(name: &str, path: &str) -> TapeTrack {
        TapeTrack {
            title: name.to_string(),
            artist: None,
            filename: format!("{name}.mp3"),
            path: PathBuf::from(path),
            extension: "mp3".to_string(),
            size_bytes: 42,
            modified: Some(SystemTime::UNIX_EPOCH),
            duration_hint: None,
        }
    }

    fn archive_with_track() -> TapeArchive {
        let mut archive = TapeArchive::new("recordings");
        archive.folders = vec![TapeFolder {
            name: "Synthwave".to_string(),
            path: PathBuf::from("recordings/Synthwave"),
            tracks: vec![test_track("Track", "recordings/Synthwave/Track.mp3")],
            expanded: true,
        }];
        archive.status = TapeArchiveStatus::Ready;
        archive.rebuild_flattened();
        archive
    }

    fn test_app() -> App {
        App::new(Library::in_memory(vec![]))
    }

    #[test]
    fn entering_tape_archive_requests_scan_when_not_loaded() {
        let mut app = test_app();
        app.active_deck_page = 1;

        app.request_tape_archive_scan_if_needed();

        assert!(app.tape_archive_scan_requested);
    }

    #[test]
    fn next_and_prev_tape_archive_rows_wrap() {
        let mut app = test_app();
        app.tape_archive = archive_with_track();
        app.active_deck_page = 1;

        app.prev_tape_archive_row();
        assert_eq!(app.tape_archive.selected, 2);

        app.next_tape_archive_row();
        assert_eq!(app.tape_archive.selected, 0);
    }

    #[test]
    fn toggle_folder_rebuilds_flattened_rows() {
        let mut app = test_app();
        app.tape_archive = archive_with_track();
        app.tape_archive.selected = 1;

        app.play_selected_tape_or_toggle();

        assert_eq!(app.tape_archive.flattened.len(), 2);
    }

    #[test]
    fn selected_track_path_returns_track_only() {
        let mut app = test_app();
        app.tape_archive = archive_with_track();

        app.tape_archive.selected = 1;
        assert!(app.tape_archive_selected_track_path().is_none());

        app.tape_archive.selected = 2;
        assert_eq!(
            app.tape_archive_selected_track_path(),
            Some(PathBuf::from("recordings/Synthwave/Track.mp3"))
        );
    }
}
