//! The root command and base/supporting types for parsing options of the CLI.

use std::{
    fmt,
    ops::Deref,
    path::{Path, PathBuf},
};

use clap::{Subcommand, ValueEnum};
use console::style;

use crate::{explain, guess_version, lint, price, Error};

/// The root command of the CLI.
#[derive(Subcommand)]
pub enum Command {
    /// Explain the given tariff in human language.
    Explain(explain::Command),

    /// Guess the version of the given CDR or Tariff.
    GuessVersion(guess_version::Command),

    /// Lint the given tariff and display a list of warnings.
    Lint(lint::Command),

    /// Price a given charge detail record (CDR) against either a provided tariff structure or a
    /// tariff that is contained in the CDR itself.
    ///
    /// This command will show you a breakdown of all the calculated costs.
    Price(price::Command),
}

impl Command {
    /// Run the root command.
    pub fn run(self) -> Result<(), Error> {
        match self {
            Self::Explain(command) => command.run(),
            Self::GuessVersion(command) => command.run(),
            Self::Lint(command) => command.run(),
            Self::Price(command) => command.run(),
        }
    }
}

/// The OCPI versions supported by this crate.
///
/// Note: The `cli` crate defines it's own `Version` as it needs to impl `clap::ValueEnum`
/// for it so the `Version` can take part in the `clap` system.
#[derive(Clone, Debug, ValueEnum)]
pub(crate) enum Version {
    /// OCPI version 2.2.1.
    ///
    /// See: <https://github.com/ocpi/ocpi/tree/release-2.2.1-bugfixes>.
    V221,

    /// OCPI version 2.1.1.
    ///
    /// See: <https://github.com/ocpi/ocpi/tree/release-2.1.1-bugfixes>.
    V211,
}

impl From<Version> for ocpi_tariffs::Version {
    fn from(value: Version) -> Self {
        match value {
            Version::V221 => ocpi_tariffs::Version::V221,
            Version::V211 => ocpi_tariffs::Version::V211,
        }
    }
}

/// A valid filepath where the source `path` has been canonicalized and the `filename` and `dirname`
/// has been extracted successfully.
#[derive(Clone, Debug)]
pub(crate) struct FilePath {
    /// The source path.
    path: PathBuf,

    /// The filename of the source path.
    file_name: String,

    /// The directory name of the source path.
    dir_name: String,
}

impl FilePath {
    /// Create a new `FilePath` from a `PathBuf` and assert that the path has a valid filename.
    pub fn from_path(path: &Path) -> Result<Self, Error> {
        let path = path.canonicalize().map_err(Error::then_file_io(path))?;
        let mut parts = path.iter().rev();

        let Some(file_name) = parts.next() else {
            return Err(Error::PathNotFile { path });
        };

        let Some(dir_name) = parts.next() else {
            return Err(Error::PathNoParentDir { path });
        };

        let file_name = file_name.to_string_lossy().to_string();
        let dir_name = dir_name.to_string_lossy().to_string();

        Ok(Self {
            path,
            file_name,
            dir_name,
        })
    }

    /// Return the filename.
    pub fn file_name(&self) -> &str {
        &self.file_name
    }

    /// Return the directory and filename parts of the path.
    pub fn dir_and_name(&self) -> String {
        format!("{}/{}", self.dir_name, self.file_name)
    }
}

impl fmt::Display for FilePath {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.path.display())
    }
}

impl AsRef<Path> for FilePath {
    fn as_ref(&self) -> &Path {
        &self.path
    }
}

impl Deref for FilePath {
    type Target = PathBuf;

    fn deref(&self) -> &Self::Target {
        &self.path
    }
}

/// Create a job description for display on the CLI.
pub fn job_description(cdr: Option<&FilePath>, tariff: Option<&FilePath>) -> String {
    let cdr_name = cdr
        .as_ref()
        .map(|c| c.file_name())
        .unwrap_or_else(|| "<stdin>");

    let tariff_name = tariff
        .as_ref()
        .map(|c| c.file_name())
        .unwrap_or_else(|| "<CDR-tariff>");

    format!(
        "`{}` with tarifff `{}`",
        style(cdr_name).blue(),
        style(tariff_name).blue(),
    )
}
