//! ## FileTransferActivity
//!
//! `filetransfer_activity` is the module which implements the Filetransfer activity, which is the main activity afterall

// This module is split into files, cause it's just too big
mod actions;
mod components;
mod fswatcher;
mod lib;
mod misc;
mod session;
mod update;
mod view;

// locals
use std::collections::VecDeque;
use std::path::PathBuf;
use std::time::Duration;

// Includes
use chrono::{DateTime, Local};
use lib::browser;
use lib::browser::{Browser, FileExplorerTab};
use lib::pane::Pane;
use lib::transfer::{TransferOpts, TransferStates};
use lib::walkdir::WalkdirStates;
use session::TransferPayload;
use tempfile::TempDir;
use tuirealm::application::Application;
use tuirealm::event::NoUserEvent;
use tuirealm::listener::EventListenerCfg;
use tuirealm::terminal::TerminalAdapter;

use super::{Activity, CROSSTERM_MAX_POLL, Context, ExitReason};
use crate::config::themes::Theme;
use crate::explorer::{FileExplorer, FileSorting};
use crate::filetransfer::{
    FileTransferParams, HostBridgeBuilder, HostBridgeParams, RemoteFsBuilder,
};
use crate::host::RemoteBridged;
use crate::system::config_client::ConfigClient;
use crate::system::watcher::FsWatcher;

// -- components

#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum MarkQueue {
    Local,
    Remote,
}

#[derive(Debug, Eq, PartialEq, Clone, Hash)]
enum Id {
    ChmodPopup,
    CopyPopup,
    DeletePopup,
    DisconnectPopup,
    ErrorPopup,
    ExplorerFind,
    ExplorerHostBridge,
    ExplorerRemote,
    FatalPopup,
    FileInfoPopup,
    FilterPopup,
    FooterBar,
    GlobalListener,
    GotoPopup,
    KeybindingsPopup,
    Log,
    MkdirPopup,
    NewfilePopup,
    OpenWithPopup,
    TransferProgressBarFull,
    TransferProgressBarPartial,
    QuitPopup,
    RenamePopup,
    ReplacePopup,
    SaveAsPopup,
    SortingPopup,
    StatusBarHostBridge,
    StatusBarRemote,
    SymlinkPopup,
    SyncBrowsingMkdirPopup,
    TerminalHostBridge,
    TerminalRemote,
    TransferQueueHostBridge,
    TransferQueueRemote,
    WaitPopup,
    WatchedPathsList,
    WatcherPopup,
}

#[derive(Debug, PartialEq)]
enum Msg {
    PendingAction(PendingActionMsg),
    Transfer(TransferMsg),
    Ui(UiMsg),
    None,
}

#[derive(Debug, PartialEq)]
enum PendingActionMsg {
    CloseSyncBrowsingMkdirPopup,
    MakePendingDirectory,
    /// Replace file popup
    ReplaceCancel,
    ReplaceOverwrite,
    ReplaceOverwriteAll,
    ReplaceSkip,
    ReplaceSkipAll,
}

#[derive(Debug, PartialEq)]
enum TransferMsg {
    AbortWalkdir,
    AbortTransfer,
    Chmod(remotefs::fs::UnixPex),
    CopyFileTo(String),
    CreateSymlink(String),
    DeleteFile,
    EnterDirectory,
    ExecuteCmd(String),
    GetFileSize,
    GoTo(String),
    GoToParentDirectory,
    GoToPreviousDirectory,
    InitFuzzySearch,
    Mkdir(String),
    NewFile(String),
    OpenFile,
    OpenFileWith(String),
    OpenTextFile,
    ReloadDir,
    RenameFile(String),
    RescanGotoFiles(PathBuf),
    SaveFileAs(String),
    ToggleWatch,
    ToggleWatchFor(usize),
    TransferFile,
}

#[derive(Debug, PartialEq)]
enum UiMsg {
    BottomPanelLeft,
    BottomPanelRight,
    ChangeFileSorting(FileSorting),
    ChangeTransferWindow,
    CloseChmodPopup,
    CloseCopyPopup,
    CloseDeletePopup,
    CloseDisconnectPopup,
    CloseErrorPopup,
    CloseExecPopup,
    CloseFatalPopup,
    CloseFileInfoPopup,
    CloseFileSortingPopup,
    CloseFilterPopup,
    CloseFindExplorer,
    CloseGotoPopup,
    CloseKeybindingsPopup,
    CloseMkdirPopup,
    CloseNewFilePopup,
    CloseOpenWithPopup,
    CloseQuitPopup,
    CloseRenamePopup,
    CloseSaveAsPopup,
    CloseSymlinkPopup,
    CloseWatchedPathsList,
    CloseWatcherPopup,
    Disconnect,
    FilterFiles(String),
    FuzzySearch(String),
    LogBackTabbed,
    /// Mark file on the list; usize is the index of the file
    MarkFile(usize),
    MarkRemove(MarkQueue, PathBuf),
    /// Mark all file at tab
    MarkAll,
    /// Clear all marks
    MarkClear,

    Quit,
    ShowChmodPopup,
    ShowCopyPopup,
    ShowDeletePopup,
    ShowDisconnectPopup,
    ShowTerminal,
    ShowFileInfoPopup,
    ShowFileSortingPopup,
    ShowFilterPopup,
    ShowGotoPopup,
    ShowKeybindingsPopup,
    GoToTransferQueue,
    ShowMkdirPopup,
    ShowNewFilePopup,
    ShowOpenWithPopup,
    ShowQuitPopup,
    ShowRenamePopup,
    ShowSaveAsPopup,
    ShowSymlinkPopup,
    ShowWatchedPathsList,
    ShowWatcherPopup,
    ToggleHiddenFiles,
    ToggleSyncBrowsing,
    WindowResized,
}

/// Log level type
enum LogLevel {
    Error,
    Warn,
    Info,
}

/// Log record entry
struct LogRecord {
    pub time: DateTime<Local>,
    pub level: LogLevel,
    pub msg: String,
}

impl LogRecord {
    /// Instantiates a new LogRecord
    pub fn new(level: LogLevel, msg: String) -> LogRecord {
        LogRecord {
            time: Local::now(),
            level,
            msg,
        }
    }
}

/// FileTransferActivity is the data holder for the file transfer activity
pub struct FileTransferActivity {
    /// Exit reason
    exit_reason: Option<ExitReason>,
    /// Context holder
    context: Option<Context>,
    /// Tui-realm application
    app: Application<Id, Msg, NoUserEvent>,
    /// Whether should redraw UI
    redraw: bool,
    /// Browser
    browser: Browser,
    /// Current log lines
    log_records: VecDeque<LogRecord>,
    /// Fuzzy search states
    walkdir: WalkdirStates,
    /// Transfer states
    transfer: TransferStates,
    /// Temporary directory where to store temporary stuff
    cache: Option<TempDir>,
    /// Fs watcher
    fswatcher: Option<FsWatcher>,
}

impl FileTransferActivity {
    /// Instantiates a new FileTransferActivity
    pub fn new(
        host_bridge_params: HostBridgeParams,
        remote_params: &FileTransferParams,
        ticks: Duration,
    ) -> Result<Self, String> {
        // Get config client
        let config_client: ConfigClient = Self::init_config_client();
        // init host bridge
        let host_bridge = HostBridgeBuilder::build(host_bridge_params, &config_client)?;
        let host_bridge_connected = host_bridge.is_localhost();
        let enable_fs_watcher = host_bridge.is_localhost();
        // Build remote client, wrapped as HostBridge via RemoteBridged
        let remote_client = RemoteFsBuilder::build(
            remote_params.protocol,
            remote_params.params.clone(),
            &config_client,
        )?;
        let remote_fs: Box<dyn crate::host::HostBridge> =
            Box::new(RemoteBridged::from(remote_client));
        // Build panes
        let local_pane = Pane::new(
            Browser::build_local_explorer(&config_client),
            host_bridge_connected,
            host_bridge,
        );
        let remote_pane = Pane::new(
            Browser::build_remote_explorer(&config_client),
            false,
            remote_fs,
        );
        Ok(Self {
            exit_reason: None,
            context: None,
            app: Application::init(
                EventListenerCfg::default().crossterm_input_listener(ticks, CROSSTERM_MAX_POLL),
            ),
            redraw: true,
            browser: Browser::new(local_pane, remote_pane),
            log_records: VecDeque::with_capacity(256),
            walkdir: WalkdirStates::default(),
            transfer: TransferStates::default(),
            cache: TempDir::new().ok(),
            fswatcher: if enable_fs_watcher {
                FsWatcher::init(Duration::from_secs(5)).ok()
            } else {
                None
            },
        })
    }

    /// Returns `true` when the active tab targets the local side
    /// (either the main host-bridge pane or a find-result rooted there).
    fn is_local_tab(&self) -> bool {
        matches!(
            self.browser.tab(),
            FileExplorerTab::HostBridge | FileExplorerTab::FindHostBridge
        )
    }

    fn host_bridge(&self) -> &FileExplorer {
        self.browser.host_bridge()
    }

    fn host_bridge_mut(&mut self) -> &mut FileExplorer {
        self.browser.host_bridge_mut()
    }

    fn remote(&self) -> &FileExplorer {
        self.browser.remote()
    }

    fn remote_mut(&mut self) -> &mut FileExplorer {
        self.browser.remote_mut()
    }

    fn found(&self) -> Option<&FileExplorer> {
        self.browser.found()
    }

    fn found_mut(&mut self) -> Option<&mut FileExplorer> {
        self.browser.found_mut()
    }

    /// Enqueue a file to be transferred
    fn enqueue_file(&mut self, index: usize) {
        let Some(src) = self
            .browser
            .explorer()
            .get(index)
            .map(|item| item.path().to_path_buf())
        else {
            return;
        };

        if self.browser.explorer().enqueued().contains_key(&src) {
            debug!("File already marked, unmarking {}", src.display());
            self.browser.explorer_mut().dequeue(&src);
        } else {
            debug!("Marking file {}", src.display());
            // Destination is the other pane's working directory joined with the file
            // name, so the queued entry holds the full target path (not just the dir).
            let mut dest = self.browser.other_explorer_no_found().wrkdir.clone();
            if let Some(name) = src.file_name() {
                dest.push(name);
            }
            self.browser.explorer_mut().enqueue(&src, &dest);
        }
        self.reload_browser_file_list();
        self.refresh_host_bridge_transfer_queue();
        self.refresh_remote_transfer_queue();
    }

    fn enqueue_all(&mut self) {
        let dest = self.browser.other_explorer_no_found().wrkdir.clone();
        self.browser.explorer_mut().enqueue_all(&dest);
        self.reload_browser_file_list();
        self.refresh_host_bridge_transfer_queue();
        self.refresh_remote_transfer_queue();
    }

    fn clear_queue(&mut self) {
        self.browser.explorer_mut().clear_queue();
        self.reload_browser_file_list();
        self.refresh_host_bridge_transfer_queue();
        self.refresh_remote_transfer_queue();
    }

    /// Get file name for a file in cache
    fn get_cache_tmp_name(&self, name: &str, file_type: Option<&str>) -> Option<String> {
        self.cache.as_ref().map(|_| {
            let base: String = format!(
                "{}-{}",
                name,
                std::time::SystemTime::now()
                    .duration_since(std::time::UNIX_EPOCH)
                    .unwrap()
                    .as_millis()
            );
            match file_type {
                None => base,
                Some(file_type) => format!("{base}.{file_type}"),
            }
        })
    }

    /// Returns a reference to context
    fn context(&self) -> &Context {
        self.context.as_ref().unwrap()
    }

    /// Returns a mutable reference to context
    fn context_mut(&mut self) -> &mut Context {
        self.context.as_mut().unwrap()
    }

    /// Returns config client reference
    fn config(&self) -> &ConfigClient {
        self.context().config()
    }

    /// Get a reference to `Theme`
    fn theme(&self) -> &Theme {
        self.context().theme_provider().theme()
    }

    /// Map a function to fs watcher if any
    fn map_on_fswatcher<F, T>(&mut self, mapper: F) -> Option<T>
    where
        F: FnOnce(&mut FsWatcher) -> T,
    {
        self.fswatcher.as_mut().map(mapper)
    }
}

/**
 * Activity Trait
 * Keep it clean :)
 * Use methods instead!
 */
impl Activity for FileTransferActivity {
    /// `on_create` is the function which must be called to initialize the activity.
    /// `on_create` must initialize all the data structures used by the activity
    fn on_create(&mut self, context: Context) {
        debug!("Initializing activity...");
        // Set context
        self.context = Some(context);
        // Clear terminal
        if let Err(err) = self.context.as_mut().unwrap().terminal().clear_screen() {
            error!("Failed to clear screen: {}", err);
        }
        // Put raw mode on enabled
        if let Err(err) = self.context_mut().terminal().enable_raw_mode() {
            error!("Failed to enter raw mode: {}", err);
        }
        // Get files at current pwd
        if self.browser.local_pane().fs.is_localhost() {
            debug!("Reloading host bridge directory");
            self.reload_host_bridge_dir();
        }
        debug!("Read working directory");
        // Configure text editor
        self.setup_text_editor();
        debug!("Setup text editor");
        // init view
        self.init();
        debug!("Initialized view");
        // Verify error state from context
        if let Some(err) = self.context.as_mut().unwrap().error() {
            error!("Fatal error on create: {}", err);
            self.mount_fatal(&err);
        }
        info!("Created FileTransferActivity");
    }

    /// `on_draw` is the function which draws the graphical interface.
    /// This function must be called at each tick to refresh the interface
    fn on_draw(&mut self) {
        // Context must be something
        if self.context.is_none() {
            return;
        }
        // Check if connected to host bridge (popup must be None, otherwise would try reconnecting in loop in case of error)
        if (!self.browser.local_pane_mut().fs.is_connected()
            || !self.browser.local_pane().connected)
            && !self.app.mounted(&Id::FatalPopup)
            && !self.browser.local_pane().fs.is_localhost()
        {
            let host_bridge_params = self.context().host_bridge_params().unwrap();
            let msg: String = match host_bridge_params.protocol_params() {
                Some(ft_params) => Self::get_connection_msg(ft_params),
                None => String::from("Connecting..."),
            };
            // Set init state to connecting popup
            self.mount_blocking_wait(msg.as_str());
            // Connect to remote
            self.connect_to_host_bridge();
            // Redraw
            self.redraw = true;
        }
        // Check if connected to remote (popup must be None, otherwise would try reconnecting in loop in case of error)
        if (!self.browser.remote_pane_mut().fs.is_connected()
            || !self.browser.remote_pane().connected)
            && !self.app.mounted(&Id::FatalPopup)
            && self.browser.local_pane_mut().fs.is_connected()
        {
            let ftparams = self.context().remote_params().unwrap();
            // print params
            let msg: String = Self::get_connection_msg(&ftparams.params);
            // Set init state to connecting popup
            self.mount_blocking_wait(msg.as_str());
            // Connect to remote
            self.connect_to_remote();
            // Redraw
            self.redraw = true;
        }
        self.tick();
        // poll
        self.poll_watcher();
        // View
        if self.redraw {
            self.view();
        }
    }

    /// `will_umount` is the method which must be able to report to the activity manager, whether
    /// the activity should be terminated or not.
    /// If not, the call will return `None`, otherwise return`Some(ExitReason)`
    fn will_umount(&self) -> Option<&ExitReason> {
        self.exit_reason.as_ref()
    }

    /// `on_destroy` is the function which cleans up runtime variables and data before terminating the activity.
    /// This function must be called once before terminating the activity.
    fn on_destroy(&mut self) -> Option<Context> {
        // Destroy cache
        if let Some(cache) = self.cache.take()
            && let Err(err) = cache.close()
        {
            error!("Failed to delete cache: {}", err);
        }
        // Disable raw mode
        if let Err(err) = self.context_mut().terminal().disable_raw_mode() {
            error!("Failed to disable raw mode: {}", err);
        }
        if let Err(err) = self.context_mut().terminal().clear_screen() {
            error!("Failed to clear screen: {}", err);
        }
        // Disconnect remote
        if self.browser.remote_pane_mut().fs.is_connected() {
            let _ = self.browser.remote_pane_mut().fs.disconnect();
        }
        // Disconnect host bridge
        if self.browser.local_pane_mut().fs.is_connected() {
            let _ = self.browser.local_pane_mut().fs.disconnect();
        }
        self.context.take()
    }
}

/// Log a UI operation error instead of panicking.
fn ui_result<T>(result: Result<T, impl std::fmt::Display>) {
    if let Err(err) = result {
        error!("UI operation failed: {err}");
    }
}
