use std::{io, num, string};

use chrono::NaiveDateTime;
use chrono_tz::Tz;
use thiserror::Error as ThisError;

use crate::chopper::types::Nanos;

pub type Result<T> = std::result::Result<T, Error>;

#[derive(ThisError, Debug)]
#[non_exhaustive]
pub enum Error {
    #[error(transparent)]
    CliParsing(#[from] clap::Error),
    #[error(transparent)]
    Io(#[from] io::Error), //TODO there is some code that uses this instead of a more specific error
    #[error(transparent)]
    TimeParsing(#[from] chrono::ParseError),
    #[error(transparent)]
    NumParseInt(#[from] num::ParseIntError),
    #[error(transparent)]
    TryFromIntError(#[from] num::TryFromIntError),
    #[error(transparent)]
    FromUtf8Error(#[from] string::FromUtf8Error),
    #[error(transparent)]
    InvalidUtf16Tuple(#[from] encode_unicode::error::InvalidUtf16Tuple),
    #[error(transparent)]
    ShapeError(#[from] ndarray::ShapeError),
    #[error("Failed to find column named '{0}'.")]
    ColumnMissing(String),
    #[error(
        "TimeZone is needed to parse a date/time value of '{0}'. None was provided. \
        Either provide it by setting -z command line arg or by setting CHOPPER_TZ env var."
    )]
    TimeZoneMissingForParsing(NaiveDateTime),
    #[error(
        "Timezone is needed to convert timestamp {0} to a human-readable time for output. \
        None was provided. Either provide it by setting -z command line arg or by setting \
        CHOPPER_TZ env var or by using --epoch for raw timestamps."
    )]
    TimeZoneMissingForOutput(Nanos),
    #[error("Converting '{0}' in timezone '{1}' to timestamp failed.")]
    TimeConversion(NaiveDateTime, Tz),
    #[error("DCFactory is required to handle DC files.")]
    DCFactoryMissing,
    #[error("Error: {0}")]
    Custom(String),
}

impl From<String> for Error {
    fn from(err: String) -> Error {
        Error::Custom(err)
    }
}

impl<'a> From<&'a str> for Error {
    fn from(err: &'a str) -> Error {
        Error::Custom(err.to_owned())
    }
}
