//! Module for parsing and manipulating QVF (Queensland Remote Sensing Centre) filenames.
//!
//! QVF is the file naming convention developed by the Queensland Remote Sensing Centre (RSC)
//! for satellite imagery archives. It encodes satellite, instrument, product, scene ID, date,
//! stage code, zone, and extension in a structured format.
//!
//! Formal reference: Flood, N. & Danaher, T. "A File Naming Convention for a Large Satellite
//! Imagery Archive: QVF — a case study."
//!
//! # Example
//!
//! ```ignore
//! use std::str::FromStr;
//! use rss_core::qvf::QvfFilename;
//!
//! let qvf = QvfFilename::from_str("cfmsre_t56jmr_20220104_abam5.img")?;
//! println!("Scene: {}, Date: {}", qvf.scene, qvf.date);
//! ```
use crate::query::{DbConnection, ImageQuery};
use anyhow::{bail, Result};
use chrono::NaiveDate;
use glob::glob;
use log::debug;
use spinners::{Spinner, Spinners};
use stac::{Item, ItemCollection};
use std::fmt::{self, Debug};

use std::process::Command;
use std::{path::PathBuf, str::FromStr};
use serde::{Deserialize, Serialize};
use strum::IntoEnumIterator;
use strum_macros::EnumString;
use strum_macros::{Display, EnumIter};

/// Satellite platform identifiers used in QVF filenames.
///
/// Each variant corresponds to a 2-character code in the filename:
/// - `l5`/`l7`/`l8`/`l9`: Landsat 5/7/8/9
/// - `lz`: Landsat mosaic
/// - `ce`/`cf`/`cv`: Sentinel-2 (Copa/Copernicus variants)
#[derive(Debug, PartialEq, EnumString, Clone)]
#[allow(non_camel_case_types)]
pub enum Satellite {
    /// Landsat 5
    l5,
    /// Landsat 7
    l7,
    /// Landsat 8
    l8,
    /// Landsat 9
    l9,
    /// Landsat mosaic
    lz,
    /// Sentinel-2 (Copernicus variant E)
    ce,
    /// Sentinel-2 (Copernicus variant F)
    cf,
    /// Sentinel-2 (Copernicus variant V, mosaic)
    cv,
}

/// Sensor instrument identifiers used in QVF filenames.
///
/// Each variant corresponds to a 2-character code in the filename.
#[derive(Debug, PartialEq, EnumString, Clone)]
#[allow(non_camel_case_types)]
pub enum Instrument {
    /// Thematic Mapper (Landsat 5/7)
    tm,
    /// MultiSpectral (Sentinel-2)
    ms,
    /// Operational Land Imager (Landsat 8/9)
    ol,
}

/// Product type identifiers used in QVF filenames.
///
/// Each variant corresponds to a 2-character code in the filename.
#[derive(Debug, PartialEq, EnumString, Clone)]
#[allow(non_camel_case_types)]
pub enum Product {
    /// Reflectance product
    re,
    /// Panchromatic product
    pa,
    /// Thermal product
    th,
}

/// File extensions used in QVF filenames.
///
/// Iterated when scanning folders for QVF files.
#[derive(Debug, PartialEq, EnumString, Clone, EnumIter)]
#[allow(non_camel_case_types)]
pub enum Extension {
    /// GeoTIFF
    tif,
    /// Erdas Imagine
    img,
    /// GeoPackage
    gpkg,
    /// Metadata file
    meta,
    /// GDAL Virtual Raster
    vrt,
}

/// A collection of parsed [`QvfFilename`] items.
///
/// Supports sorting, filtering, recalling from filestore, and conversion
/// to STAC ItemCollection.
#[derive(Debug, PartialEq, Clone)]
pub struct QvfFilenames {
    /// The list of parsed QVF filenames.
    pub qvf_filenames: Vec<QvfFilename>,
}

/// Builder for constructing [`QvfFilenames`] from a directory of files.
///
/// # Example
/// ```ignore
/// let qvfs = QvfFilenamesBuilder::from_folder(&PathBuf::from("/data/scenes"))
///     .build();
/// ```
#[derive(Debug, PartialEq, Clone)]
pub struct QvfFilenamesBuilder {
    qvf_filenames: Vec<QvfFilename>,
}

impl QvfFilenamesBuilder {
    /// Builds a QvfFilenames collection from all QVF files in a folder.
    ///
    /// Searches recursively for files with extensions: tif, img, gpkg, meta, vrt.
    ///
    /// # Example
    /// ```ignore
    /// let qvfs = QvfFilenamesBuilder::from_folder(&PathBuf::from("/data/scenes"))
    ///     .build();
    /// ```
    pub fn from_folder(folder: &Path) -> QvfFilenamesBuilder {
        let mut sources = Vec::new();
        for ext in Extension::iter() {
            let pattern = format!("{}/*.{ext:?}", folder.to_str().unwrap());
            let source_files: Result<Vec<_>, _> = glob(&pattern)
                .expect("Failed to read glob pattern")
                .collect();
            let source_files: Vec<PathBuf> = source_files.unwrap();
            for s in source_files {
                sources.push(s);
            }
        }

        let mut result = Vec::new();

        for sf in sources.iter() {
            let file = sf.to_str().unwrap();
            result.push(QvfFilename::from_str(file).unwrap())
        }
        QvfFilenamesBuilder {
            qvf_filenames: result,
        }
    }
    /// Builds the final QvfFilenames collection.
    pub fn build(self) -> QvfFilenames {
        let mut qs = Vec::new();
        for q in self.qvf_filenames {
            qs.push(q)
        }
        QvfFilenames { qvf_filenames: qs }
    }
}

/// Fields available for sorting [`QvfFilenames`] collections.
///
/// Currently only `Date` is fully implemented; other variants will panic.
pub enum QvfFields {
    /// Sort by satellite platform
    Satellite,
    /// Sort by instrument
    Instrument,
    /// Sort by product type
    Product,
    /// Sort by scene ID
    Scene,
    /// Sort by acquisition date (implemented)
    Date,
    /// Sort by stage code
    StageCode,
    /// Sort by UTM/MGRS zone
    Zone,
    /// Sort by extra fields
    ExtraFields,
}

/// A range of dates used for mosaic QVF filenames.
///
/// Encoded in the filename as `m{start}{end}` (e.g., `m202201202202`).
#[derive(Debug, PartialEq, Clone, Copy, PartialOrd, Eq, Hash)]
pub struct QvfDateRange {
    /// The start date of the range.
    pub start: NaiveDate,
    /// The end date of the range.
    pub end: NaiveDate,
}

/// Date representation in QVF filenames.
///
/// Can be a single date (scene images) or a date range (mosaic images).
#[derive(Debug, PartialEq, Clone, Copy, PartialOrd, Display, Eq, Hash)]
pub enum QvfDate {
    /// A single acquisition date (for scene images).
    Date(NaiveDate),
    /// A date range (for mosaic images).
    DateRange(QvfDateRange),
}

impl QvfDate {
    /// Formats this date using the given format string.
    ///
    /// For single dates, delegates to [`NaiveDate::format`].
    /// For date ranges, produces `m{start}{end}` format.
    ///
    /// # Example
    /// ```ignore
    /// let date = QvfDate::Date(NaiveDate::from_ymd(2022, 1, 4));
    /// assert_eq!(date.format("%Y%m%d"), "20220104");
    /// ```
    pub fn format(&self, formatter: &str) -> String {
        let formated = match self {
            QvfDate::Date(d) => d.format(formatter).to_string(),
            QvfDate::DateRange(d) => {
                let s = d.start.format(formatter).to_string();
                let e = d.end.format(formatter).to_string();
                format!("m{s}{e}")
            }
        };
        formated
    }
}
impl From<QvfDate> for NaiveDate {
    fn from(item: QvfDate) -> Self {
        match item {
            QvfDate::Date(d) => d,
            QvfDate::DateRange(d) => d.start,
        }
    }
}

/// Satellite collection types supported by the query builder.
///
/// Used to resolve STAC collection IDs per provider.
#[derive(Debug, PartialEq, Copy, Clone, clap::ValueEnum, EnumIter, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Collection {
    /// Landsat 5 collection
    Landsat5,
    /// Landsat 7 collection
    Landsat7,
    /// Landsat 8 collection
    Landsat8,
    /// Landsat 9 collection
    Landsat9,
    /// All Landsat collections (5/7/8/9)
    LandsatAll,
    /// Sentinel-2 collection
    Sentinel2,
}

/// Type of satellite imagery encoded in the QVF filename.
#[derive(Debug, PartialEq, Clone)]
pub enum ImageType {
    /// A single satellite scene (e.g., Landsat or Sentinel-2 acquisition)
    Scene,
    /// A composite mosaic spanning multiple scenes or dates
    Mosaic,
}

/// A parsed QVF filename containing all fields from the naming convention.
///
/// QVF filenames follow the pattern:
/// `{satellite}{instrument}{product}_{scene}_{date}_{stage}{zone}.{ext}`
/// For example: `cfmsre_t56jmr_20220104_abam5.img`
///
/// # Fields
///
/// | Field | Example | Description |
/// |-------|---------|-------------|
/// | `satellite` | `cf` | Satellite platform code |
/// | `instrument` | `ms` | Sensor instrument code |
/// | `product` | `re` | Product type code |
/// | `scene` | `t56jmr` | Scene/tile identifier |
/// | `date` | `20220104` | Acquisition date or range |
/// | `stage_code` | `aba` | Processing stage code |
/// | `zone` | `m5` | UTM/MGRS zone |
/// | `extension` | `img` | File extension |
///
/// # Example
///
/// ```ignore
/// use std::str::FromStr;
/// use rss_core::qvf::QvfFilename;
///
/// let qvf = QvfFilename::from_str("cfmsre_t56jmr_20220104_abam5.img")?;
/// assert_eq!(qvf.scene, "t56jmr");
/// assert!(qvf.is_sentinel());
/// ```
#[derive(Debug, PartialEq, Clone)]
pub struct QvfFilename {
    /// Satellite platform (e.g., `cf` for Sentinel-2)
    pub satellite: Satellite,
    /// Sensor instrument (e.g., `ms` for MultiSpectral)
    pub instrument: Instrument,
    /// Product type (e.g., `re` for reflectance)
    pub product: Product,
    /// Scene/tile identifier (e.g., `t56jmr` or `p091r077`)
    pub scene: String,
    /// Acquisition date or date range
    pub date: QvfDate,
    /// Processing stage code (e.g., `aba`, `aba`, `L2A`)
    pub stage_code: String,
    /// UTM/MGRS zone (e.g., `m5`)
    pub zone: String,
    /// File extension
    pub extension: Extension,
    /// Derived collection type (Landsat or Sentinel-2)
    pub collection: Collection,
    /// Whether this is a scene or mosaic image
    pub image_type: ImageType,
    /// Optional file system location
    pub location: Option<PathBuf>,
    /// Optional extra fields (for mosaic filenames with additional data)
    pub extra_fields: Option<Vec<String>>,
}

impl FromStr for QvfFilename {
    type Err = anyhow::Error;

    fn from_str(string: &str) -> Result<QvfFilename> {
        let path = PathBuf::from(string);
        let location = path.parent();
        let location = location.map(PathBuf::from);

        let string = path.file_name().unwrap().to_str().unwrap();

        let parts: Vec<&str> = string.split('.').collect();
        let name: &str = parts[0];
        let ext: &str = parts[1];

        let qvf_parts: Vec<&str> = name.split('_').collect();

        let satellite = Satellite::from_str(&qvf_parts[0][0..2])?;
        let instrument = Instrument::from_str(&qvf_parts[0][2..4])?;
        let product = Product::from_str(&qvf_parts[0][4..6])?;
        let scene = qvf_parts[1].to_string();

        let extra_fields: Option<Vec<String>> = if qvf_parts.len() > 4 {
            let fields: Vec<String> = qvf_parts[4..].iter().map(|f| f.to_string()).collect();
            Some(fields)
        } else {
            None
        };

        let date = if qvf_parts[2].len() == 8 {
            let d = NaiveDate::parse_from_str(qvf_parts[2], "%Y%m%d")
                .expect("Could not parse the date!");
            QvfDate::Date(d)
        } else if qvf_parts[2].len() == 13 {
            let start = NaiveDate::parse_from_str(&format!("{}01", &qvf_parts[2][1..7]), "%Y%m%d")
                .expect("Could not parse the date!");
            let end = NaiveDate::parse_from_str(&format!("{}01", &qvf_parts[2][7..]), "%Y%m%d")
                .expect("Could not parse the date!");
            QvfDate::DateRange(QvfDateRange { end, start })
        } else if qvf_parts[2].len() == 17 {
            let start = NaiveDate::parse_from_str(&qvf_parts[2][1..9], "%Y%m%d")
                .expect("Could not parse the date!");

            let end = NaiveDate::parse_from_str(&qvf_parts[2][9..], "%Y%m%d")
                .expect("Could not parse the date!");
            QvfDate::DateRange(QvfDateRange { end, start })
        } else {
            bail!("Could not parse the date!")
        };

        let stage_code = qvf_parts[3][0..3].to_string();
        let zone = qvf_parts[3][3..5].to_string();
        let extension = Extension::from_str(ext)?;

        let image_type = match satellite {
            Satellite::cv | Satellite::lz => ImageType::Mosaic,
            Satellite::l5
            | Satellite::l7
            | Satellite::l8
            | Satellite::ce
            | Satellite::cf
            | Satellite::l9 => ImageType::Scene,
        };
        let collection = match satellite {
            Satellite::l5 | Satellite::l7 | Satellite::l8 | Satellite::lz | Satellite::l9 => {
                Collection::Landsat8
            }
            Satellite::ce | Satellite::cf | Satellite::cv => Collection::Sentinel2,
        };
        let qvf_fn = QvfFilename {
            satellite,
            instrument,
            product,
            scene,
            date,
            stage_code,
            zone,
            extension,
            image_type,
            collection,
            location,
            extra_fields,
        };

        Ok(qvf_fn)
    }
}

impl fmt::Display for QvfFilename {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{:?}{:?}{:?}_{}_{}_{}{}.{:?}",
            self.satellite,
            self.instrument,
            self.product,
            self.scene,
            self.date,
            self.stage_code,
            self.zone,
            self.extension
        )
    }
}

impl QvfFilenames {
    /// Sorts the collection by the specified field (currently only Date is implemented).
    pub fn sort_by(&mut self, field: QvfFields) {
        // let mut qvf_fns= qvf_fns.to_owned();
        let sorted = match field {
            QvfFields::Satellite => todo!(),
            QvfFields::Instrument => todo!(),
            QvfFields::Product => todo!(),
            QvfFields::Scene => todo!(),
            QvfFields::Date => {
                let mut sorted = self.qvf_filenames.clone();
                sorted.sort_by(|a, b| {
                    let date_a = &a.date;
                    let date_b = &b.date;
                    date_a.partial_cmp(date_b).unwrap()
                });
                sorted
            }
            QvfFields::StageCode => todo!(),
            QvfFields::Zone => todo!(),
            QvfFields::ExtraFields => todo!(),
        };
        self.qvf_filenames = sorted;
    }
    /// Filters files by stage code (e.g., "L2A", "L2T").
    pub fn filter_by_stage(&mut self, value: &str) {
        // let mut qvf_fns= qvf_fns.to_owned();
        let names = self.qvf_filenames.clone();
        let filtered = names
            .into_iter()
            .filter(|q| q.stage_code == value)
            .collect();
        self.qvf_filenames = filtered;
    }
    /// Builds a [`QvfFilenames`] collection from an [`ImageQuery`] and stage codes.
    ///
    /// Queries the Apollo PostgreSQL database and returns matching filenames.
    pub fn from_image_query(query: ImageQuery, stage_codes: &[&str]) -> Self {
        let db = DbConnection::new();
        db.query_imagery(query, stage_codes)
    }

    /// Recalls all files in this collection from the Apollo filestore to a local directory.
    ///
    /// Uses `dmget` to stage files from DM storage, then copies them to the destination.
    /// Returns the list of local paths where files were copied.
    pub fn recall(self, to: &Path) -> Result<Vec<PathBuf>> {
        match self.qvf_filenames.len() {
            0 => {
                bail!("Nothing to be recalled")
            }
            _ => {
                let mut sp = Spinner::new(
                    Spinners::Earth,
                    format!(
                        "Recaling {} files, please be patient.",
                        self.qvf_filenames.len()
                    ),
                );

                let mut to_recall = Vec::new();
                let mut recalled = Vec::new();
                for q in self.qvf_filenames {
                    let base_dir = q.clone().qv_dir().unwrap();
                    let file_name = q.name();
                    let tr = base_dir.join(&file_name);
                    let r = to.join(file_name);
                    to_recall.push(tr);
                    recalled.push(r);
                }
                Command::new("dmget")
                    .args(&to_recall)
                    .spawn()
                    .expect("Failed to start recalling")
                    .wait()
                    .expect("Failed recall");
                for (t, r) in to_recall.iter().zip(recalled.iter()) {
                    if !r.exists() {
                        debug!("Copy from: {t:?} to: {r:?}");
                        std::fs::copy(t, r).expect("Could not copy files");
                    } else {
                        debug!("{r:?} alredy in dst folder");
                    }
                }
                sp.stop();
                Ok(recalled)
            }
        }
    }

    /// Converts this collection of QVF filenames to a STAC [`ItemCollection`].
    ///
    /// Opens each file with GDAL and extracts geometry, bounds, and metadata
    /// to build valid STAC items.
    pub fn to_feature_collection(&self) -> ItemCollection {
        let mut items = Vec::new();

        for qvf_file in &self.qvf_filenames {
            let p = qvf_file.location.clone().unwrap();
            let name = qvf_file.name();
            let item = gdal_to_stac_item(p.join(name)).unwrap();
            items.push(item);
        }

        let item_collection = items.into();
        debug!("Item_collection {:?}", item_collection);
        item_collection
    }
}

use anyhow::Context;
use gdal::Dataset;
use stac::{Asset, Href};
use std::path::Path;

use serde_json::{Map, Value};

/// Converts GDAL raster file to a STAC Item with geometry and basic metadata.
///
/// Extracts bounds from geo transform, creates a polygon geometry,
/// and adds the file as a STAC asset.
///
/// # Example
/// ```ignore
/// let item = gdal_to_stac_item("/data/scene.tif")?;
/// ```
pub fn gdal_to_stac_item<P: AsRef<Path>>(path: P) -> Result<Item> {
    let path_str = path.as_ref().to_str().context("Invalid path")?;

    // Open the GDAL dataset
    let ds =
        Dataset::open(&path).with_context(|| format!("Failed to open dataset at {}", path_str))?;

    let size = ds.raster_size();
    let (width, height) = size;

    let geo_transform = ds.geo_transform().context("Failed to read geo transform")?;
    let _projection = ds.projection();

    // Calculate bounding box
    let min_x = geo_transform[0];
    let max_y = geo_transform[3];
    let max_x = min_x + geo_transform[1] * width as f64;
    let min_y = max_y + geo_transform[5] * height as f64;

    let _bbox = [min_x, min_y, max_x, max_y];
    let geometry = Some(geojson::Geometry::new(geojson::Value::Polygon(vec![vec![
        vec![min_x, min_y],
        vec![min_x, max_y],
        vec![max_x, max_y],
        vec![max_x, min_y],
        vec![min_x, min_y],
    ]])));

    // Placeholder timestamp: now
    let datetime = Some("2025-01-01T00:00:00Z".parse()?);

    // Generate a unique ID from the filename or UUID
    let id = path
        .as_ref()
        .file_stem()
        .and_then(|s| s.to_str())
        .unwrap()
        .to_string();

    // Create empty item and populate fields
    // Build the eo:bands array
    let eo_bands = Value::Array(vec![Value::Object({
        let mut band = Map::new();
        band.insert(
            "name".to_string(),
            Value::String("test_band_name".to_string()),
        );
        band
    })]);
    let mut item = Item::new(id);
    // item.bbox = Some(bbox);
    item.geometry = geometry;
    item.properties.datetime = datetime;
    // Add the original file as an asset
    let mut asset = Asset::new(Href::from(path_str));
    let mut map = Map::new();
    map.insert("eo:bands".to_string(), eo_bands);
    asset.additional_fields = map;
    item.assets.insert("data".to_string(), asset);

    Ok(item)
}

impl QvfFilename {
    /// Returns a new [`QvfFilename`] with the satellite field changed.
    pub fn change_satellite(mut self, satellite: Satellite) -> Self {
        self.satellite = satellite;
        self
    }
    /// Returns a new [`QvfFilename`] with the instrument field changed.
    pub fn change_instrument(mut self, instrument: Instrument) -> Self {
        self.instrument = instrument;
        self
    }
    /// Returns a new [`QvfFilename`] with the product field changed.
    pub fn change_product(mut self, product: Product) -> Self {
        self.product = product;
        self
    }
    /// Returns a new [`QvfFilename`] with the date field changed.
    pub fn change_date(mut self, date: QvfDate) -> Self {
        self.date = date;
        self
    }
    /// Returns a new [`QvfFilename`] with the stage code field changed.
    pub fn change_stage_code(mut self, code: String) -> Self {
        self.stage_code = code;
        self
    }
    /// Returns a new [`QvfFilename`] with the zone field changed.
    pub fn change_zone(mut self, zone: String) -> Self {
        self.zone = zone;
        self
    }
    /// Returns a new [`QvfFilename`] with the extension field changed.
    pub fn change_extension(mut self, extension: Extension) -> Self {
        self.extension = extension;
        self
    }

    /// Returns true if this is a Landsat satellite (l5, l7, l8, l9, lz).
    #[must_use]
    pub fn is_landsat(&self) -> bool {
        matches!(
            self.satellite,
            Satellite::l5 | Satellite::l7 | Satellite::l8 | Satellite::l9 | Satellite::lz
        )
    }

    /// Returns true if this is a Sentinel satellite (ce, cf, cv).
    #[must_use]
    pub fn is_sentinel(&self) -> bool {
        matches!(
            self.satellite,
            Satellite::ce | Satellite::cf | Satellite::cv
        )
    }

    /// Checks if the file exists in the filestore at its expected path.
    #[must_use]
    pub fn exist_on_filestore(&self) -> bool {
        let mut location = self.qv_dir().unwrap();
        location.push(self.name());
        debug!("location, {location:?}");
        location.exists()
    }

    /// Returns the filename string (e.g., "l8ms_p094r074_20220101_L2A_34.tif").
    ///
    /// Handles both scene and mosaic image types with appropriate date formatting.
    pub fn name(&self) -> String {
        match self.image_type {
            ImageType::Scene => {
                let name = if let Some(extra) = &self.extra_fields {
                    let ef = extra.join("_");
                    format!(
                        "{:?}{:?}{:?}_{}_{}_{}{}_{}.{:?}",
                        self.satellite,
                        self.instrument,
                        self.product,
                        self.scene,
                        self.date.format("%Y%m%d"),
                        self.stage_code,
                        self.zone,
                        ef,
                        self.extension,
                    )
                } else {
                    format!(
                        "{:?}{:?}{:?}_{}_{}_{}{}.{:?}",
                        self.satellite,
                        self.instrument,
                        self.product,
                        self.scene,
                        self.date.format("%Y%m%d"),
                        self.stage_code,
                        self.zone,
                        self.extension
                    )
                };
                name
            }
            ImageType::Mosaic => {
                let name = if let Some(extra) = &self.extra_fields {
                    let ef = extra.join("_");
                    format!(
                        "{:?}{:?}{:?}_{}_{}_{}{}_{}.{:?}",
                        self.satellite,
                        self.instrument,
                        self.product,
                        self.scene,
                        self.date.format("%Y%m"),
                        self.stage_code,
                        self.zone,
                        ef,
                        self.extension,
                    )
                } else {
                    format!(
                        "{:?}{:?}{:?}_{}_{}_{}{}.{:?}",
                        self.satellite,
                        self.instrument,
                        self.product,
                        self.scene,
                        self.date.format("%Y%m"),
                        self.stage_code,
                        self.zone,
                        self.extension
                    )
                };

                name
            }
        }
    }

    /// Returns the full path by joining location with the filename.
    pub fn to_pathbuff(&self) -> PathBuf {
        let def_root = PathBuf::from(".");
        let p = self.location.clone().unwrap_or(def_root);
        p.join(self.name())
    }

    /// Returns the expected filestore directory path for this QVF file.
    ///
    /// Constructed from collection, satellite type, and date information.
    pub fn qv_dir(&self) -> Result<PathBuf> {
        let filestore_topdirs = PathBuf::from("/apollo");
        let default_collections = PathBuf::from("imagery/rsc/");
        let dirname = match self.collection {
            Collection::Sentinel2 => match self.image_type {
                ImageType::Scene => {
                    let scene_group = PathBuf::from(&self.scene[0..4]);
                    let scene = PathBuf::from(&self.scene);
                    let scene_dir = scene_group.join(scene);
                    let date_dir = PathBuf::from(&self.date.format("%Y").to_string())
                        .join(self.date.format("%Y%m"));

                    PathBuf::from("sentinel2").join(scene_dir).join(date_dir)
                }
                ImageType::Mosaic => PathBuf::from("."), //todo!(),
            },
            Collection::Landsat8
            | Collection::Landsat5
            | Collection::Landsat7
            | Collection::Landsat9
            | Collection::LandsatAll => {
                let scene_group = match self.instrument {
                    Instrument::ms => "landsat45mss",
                    Instrument::tm => "landsat57tm",
                    Instrument::ol => "landsat57tm",
                };

                match self.image_type {
                    ImageType::Scene => {
                        let wrs_dir = "wrs2";
                        let path = &self.scene[1..4];
                        let row = &self.scene[5..8];
                        let path_row_dir = format!("{path}_{row}");
                        let date_dir = PathBuf::from(&self.date.format("%Y").to_string())
                            .join(self.date.format("%Y%m"));

                        PathBuf::from("landsat")
                            .join(scene_group)
                            .join(wrs_dir)
                            .join(path_row_dir)
                            .join(date_dir)
                    }
                    ImageType::Mosaic => todo!(),
                }
            }
        };
        let dirname = filestore_topdirs.join(default_collections).join(dirname);
        Ok(dirname)
    }

    /// Recalls a file from the file store
    ///
    /// It first does dmget to make the file available on local
    /// disk and the copies the file to the destination.
    pub fn recall(self, to: PathBuf) -> Result<PathBuf> {
        let mut sp = Spinner::new(Spinners::Dots9, "Waiting for 3 seconds".into());
        let base_dir = self.clone().qv_dir().unwrap();
        let file_name = self.name();
        let to_recall = base_dir.join(&file_name);
        Command::new("dmget")
            .arg(&to_recall)
            .spawn()
            .expect("Failed to start recalling")
            .wait()
            .expect("Failed recall");
        let recalled = to.join(file_name);
        std::fs::copy(to_recall, &recalled).expect("Could not copy files");
        sp.stop();
        Ok(recalled)
    }
}
