use crate::{BaseInfo, ErrorKind, Uri, checker::wikilink::index::WikilinkIndex};
use std::path::{Path, PathBuf};

#[derive(Clone, Debug)]
pub(crate) struct WikilinkResolver {
    checker: WikilinkIndex,
    fallback_extensions: Vec<String>,
}

/// Tries to resolve a `WikiLink` by searching for the filename in the `WikilinkIndex`
/// Returns the path of the found file if found, otherwise an Error
impl WikilinkResolver {
    /// # Errors
    ///
    /// Fails if the URL within `base` is not a file:// URL.
    pub(crate) fn new(
        base: &BaseInfo,
        fallback_extensions: Vec<String>,
    ) -> Result<Self, ErrorKind> {
        let base = match base {
            BaseInfo::None => Err(ErrorKind::WikilinkInvalidBase(
                "Base must be specified for wikilink checking".into(),
            ))?,
            base => base,
        };
        let base = base.to_file_path().ok_or(ErrorKind::WikilinkInvalidBase(
            "Base cannot be remote".to_string(),
        ))?;

        Ok(Self {
            checker: WikilinkIndex::new(base),
            fallback_extensions,
        })
    }
    /// Resolves a wikilink by searching the index with fallback extensions.
    pub(crate) fn resolve(&self, path: &Path, uri: &Uri) -> Result<PathBuf, ErrorKind> {
        for ext in &self.fallback_extensions {
            let mut candidate = path.to_path_buf();
            candidate.set_extension(ext);

            if let Some(resolved) = self.checker.contains_path(&candidate) {
                return Ok(resolved);
            }
        }

        Err(ErrorKind::WikilinkNotFound(uri.clone(), path.to_path_buf()))
    }
}

#[cfg(test)]
mod tests {
    use crate::{BaseInfo, ErrorKind, Uri, checker::wikilink::resolver::WikilinkResolver};
    use test_utils::{fixture_uri, fixtures_path};

    #[test]
    fn test_wikilink_resolves_to_filename() {
        let resolver = WikilinkResolver::new(
            &BaseInfo::from_path(&fixtures_path!().join("wiki")).unwrap(),
            vec!["md".to_string()],
        )
        .unwrap();
        let uri = Uri {
            url: fixture_uri!("wiki/Usage"),
        };
        let path = fixtures_path!().join("Usage");
        let expected_result = fixtures_path!().join("wiki/Usage.md");
        assert_eq!(resolver.resolve(&path, &uri), Ok(expected_result));
    }

    #[test]
    fn test_wikilink_not_found() {
        let resolver = WikilinkResolver::new(
            &BaseInfo::from_path(&fixtures_path!().join("wiki")).unwrap(),
            vec!["md".to_string()],
        )
        .unwrap();
        let uri = Uri {
            url: fixture_uri!("wiki/404"),
        };
        let path = fixtures_path!().join("404");
        assert!(matches!(
            resolver.resolve(&path, &uri),
            Err(ErrorKind::WikilinkNotFound(..))
        ));
    }
}
