use std::{
    collections::{HashSet, VecDeque},
    env,
    fs::File,
    io::{BufRead, BufReader, BufWriter},
    path::PathBuf,
};

use cross::path::home_dir;
use stuff::{
    autocomplete,
    readline::{self, NonEmptyVec, ReadlineProvider},
};

#[derive(Default)]
pub struct ReadlineProviderImpl {
    history: ShellHistory,
}

pub struct ShellHistory {
    entries: VecDeque<String>,
    storage_path: PathBuf,
}

impl Default for ShellHistory {
    fn default() -> Self {
        // Load entries
        let storage_path = home_dir()
            .unwrap_or_else(|| PathBuf::from("/"))
            .join(".shell_history");

        let mut entries = VecDeque::new();

        if let Ok(file) = File::open(&storage_path).map(BufReader::new) {
            for line in file.lines() {
                let Ok(line) = line else { break };
                entries.push_back(line);
            }
        }

        Self {
            entries,
            storage_path,
        }
    }
}

impl Drop for ShellHistory {
    fn drop(&mut self) {
        self.flush();
    }
}

impl ShellHistory {
    const LIMIT: usize = 128;

    pub fn flush(&mut self) {
        use std::io::Write;

        let Ok(file) = File::create(&self.storage_path) else {
            log::error!(
                "Couldn't open shell history file: {}",
                self.storage_path.display()
            );
            return;
        };

        let mut writer = BufWriter::new(file);

        for line in self.entries.iter() {
            if writeln!(writer, "{line}").is_err() {
                break;
            }
        }
    }

    pub fn lookup(&self, depth: usize) -> Option<&str> {
        if depth >= self.entries.len() {
            return None;
        }
        let index = self.entries.len() - depth - 1;
        Some(&self.entries[index])
    }

    pub fn push(&mut self, entry: &str) {
        let entry = entry.trim();
        self.entries.retain(|e| e != entry);
        self.entries.push_back(entry.into());
        while self.entries.len() > Self::LIMIT {
            self.entries.pop_back();
        }
    }
}

impl ReadlineProvider for ReadlineProviderImpl {
    fn completions(&mut self, buffer: &readline::Buffer) -> Option<NonEmptyVec<String>> {
        let word_before_cursor = buffer.word_before_cursor();
        let is_first = buffer.is_first_word();

        let mut entries = HashSet::new();
        if let Some(word) = word_before_cursor
            && !is_first
        {
            autocomplete::complete_from_cwd(&mut entries, word, &Default::default());
        } else {
            let prefix = word_before_cursor.unwrap_or("");
            autocomplete::complete_binary_from_env(&mut entries, prefix);
        }

        let mut entries = NonEmptyVec::try_from_iter(entries.drain()).ok()?;
        entries.sort();

        Some(entries)
    }

    fn push_history(&mut self, entry: &str) {
        self.history.push(entry);
    }

    fn lookup_history(&mut self, depth: usize) -> Option<String> {
        self.history.lookup(depth).map(Into::into)
    }

    fn prompt(&mut self) -> String {
        let cwd = env::current_dir();
        let cwd = match cwd {
            Ok(cwd) => format!("{}", cwd.display()),
            Err(_) => "\x1B[33m???\x1B[0m".into(),
        };

        format!("{cwd} $ ")
    }
}
