//! Guess the version of a CDR or tariff.

use std::{fmt, path::PathBuf};

use console::style;
use ocpi_tariffs::{cdr, guess, json, schema, tariff, warning, Versioned as _};

use crate::{load_object_file, load_object_from_stdin, print, Error, ObjectKind};

/// The arguments for the `guess` command.
#[derive(clap::Parser)]
pub struct Command {
    /// The arguments for the `guess` command.
    #[command(flatten)]
    args: Arguments,
}

/// The arguments for the `guess` command.
#[derive(clap::Args)]
struct Arguments {
    /// The type of OCPI object contained in the given JSON.
    #[arg(short = 't', long = "type")]
    kind: ObjectKind,

    /// A path to JSON file for the specified object.
    #[arg(short = 'f', long)]
    file: Option<PathBuf>,

    /// Check if all fields are allowed to be there according to the OCPI spec.
    #[arg(long)]
    validate: bool,
}

impl fmt::Display for ObjectKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ObjectKind::Cdr => f.write_str("CDR"),
            ObjectKind::Tariff => f.write_str("tariff"),
        }
    }
}

/// The outcome of running the guess command.
struct Outcome {
    /// The kind of object given.
    object_kind: ObjectKind,

    /// The guessed version of the object given.
    version: guess::Version<ocpi_tariffs::Version, ()>,

    /// The schema validation warnings for the object.
    ///
    /// This is `None` when `--validate` was not requested, or when the guessed
    /// `Version` is `Uncertain` (there is no schema to validate against).
    warnings: Option<warning::Set<schema::Warning>>,
}

impl Command {
    /// Run the `guess` Command.
    pub fn run(self) -> Result<(), Error> {
        let Self {
            args:
                Arguments {
                    kind: object_kind,
                    file: path,
                    validate,
                },
        } = self;

        let json = if let Some(path) = path.as_deref() {
            load_object_file(path, object_kind)?
        } else {
            load_object_from_stdin(object_kind)?
        };

        let outcome = match object_kind {
            ObjectKind::Cdr => {
                let cdr = json::parse_object(&json)?;
                let guess = cdr::infer_version(cdr);
                let version = guess.as_version();

                // Validate against the schema only when the version is certain.
                let warnings = match (validate, guess) {
                    (true, guess::Version::Certain(cdr)) => {
                        let cdr_version = cdr.version();
                        let (_cdr, warnings) = cdr::build(cdr.into_doc(), cdr_version).into_parts();
                        Some(warnings)
                    }
                    _ => None,
                };

                Outcome {
                    object_kind,
                    version,
                    warnings,
                }
            }
            ObjectKind::Tariff => {
                let tariff = json::parse_object(&json)?;
                let guess = tariff::infer_version(tariff);
                let version = guess.as_version();

                // Validate against the schema only when the version is certain.
                let warnings = match (validate, guess) {
                    (true, guess::Version::Certain(tariff)) => {
                        let tariff_version = tariff.version();
                        let (_tariff, warnings) =
                            tariff::build(tariff.into_doc(), tariff_version).into_parts();
                        Some(warnings)
                    }
                    _ => None,
                };

                Outcome {
                    object_kind,
                    version,
                    warnings,
                }
            }
        };

        let Outcome {
            object_kind,
            warnings,
            version,
        } = outcome;

        if let Some(warnings) = warnings {
            print::warning_set("version guessing", &warnings);
        }

        print_version(object_kind, &version);

        Ok(())
    }
}

/// Print the version to the CLI.
fn print_version(object_kind: ObjectKind, version: &guess::Version<ocpi_tariffs::Version, ()>) {
    match version {
        guess::Version::Uncertain(()) => {
            eprintln!(
                "Unable to guess the version of the given {} JSON",
                style(object_kind).green()
            );
        }
        guess::Version::Certain(version) => {
            println!("{version}");
        }
    }
}
