use std::fs::OpenOptions;
use std::io::{Error, Result, Write};
use std::path::{Path, PathBuf};
use std::sync::OnceLock;

use chrono::Local;

/// Severity level used for filtering log messages.
///
/// Levels are ordered from least to most severe:
/// `Trace < Debug < Info < Warn < Error`.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum LogLevel {
    Trace,
    Debug,
    Info,
    Warn,
    Error,
}

impl LogLevel {
    /// All supported log levels, ordered from most to least severe.
    pub const ALL: [Self; 5] = [
        Self::Error,
        Self::Warn,
        Self::Info,
        Self::Debug,
        Self::Trace,
    ];

    /// Converts the `LogLevel` to its string representation.
    pub const fn as_str(self) -> &'static str {
        match self {
            LogLevel::Trace => "TRACE",
            LogLevel::Debug => "DEBUG",
            LogLevel::Info => "INFO",
            LogLevel::Warn => "WARN",
            LogLevel::Error => "ERROR",
        }
    }
}

impl std::fmt::Display for LogLevel {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.as_str())
    }
}

/// Returned when a string does not represent a supported log level.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseLogLevelError {
    invalid_value: String,
}

impl std::fmt::Display for ParseLogLevelError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "invalid log level '{}'. Expected one of: ERROR, WARN, INFO, DEBUG, TRACE",
            self.invalid_value
        )
    }
}

impl std::error::Error for ParseLogLevelError {}

impl std::str::FromStr for LogLevel {
    type Err = ParseLogLevelError;

    fn from_str(value: &str) -> std::result::Result<Self, Self::Err> {
        match value.trim().to_ascii_uppercase().as_str() {
            "TRACE" | "5" => Ok(Self::Trace),
            "DEBUG" | "4" => Ok(Self::Debug),
            "INFO" | "3" => Ok(Self::Info),
            "WARN" | "WARNING" | "2" => Ok(Self::Warn),
            "ERROR" | "1" => Ok(Self::Error),
            _ => Err(ParseLogLevelError {
                invalid_value: value.to_owned(),
            }),
        }
    }
}

/// A file logger that writes messages to `path` and filters by `level`.
///
/// By default, the logger starts at `LogLevel::Info`.
#[derive(Debug)]
pub struct Logger {
    path: PathBuf,
    level: LogLevel,
}

impl Logger {
    /// Creates a new `Logger` that writes to `path`.
    ///
    /// The default level is `LogLevel::Info`.
    ///
    /// # Examples
    /// ```rust
    /// use log_easy::Logger;
    /// let logger = Logger::new("app.log");
    /// ```
    #[must_use]
    pub fn new<P: Into<PathBuf>>(path: P) -> Self {
        Self {
            path: path.into(),
            level: LogLevel::Info,
        }
    }

    /// Returns a new `Logger` configured with the given minimum log level.
    ///
    /// Messages below this level are ignored.
    ///
    /// # Examples
    /// ```rust
    /// use log_easy::{Logger, LogLevel};
    /// let logger = Logger::new("app.log").with_level(LogLevel::Debug);
    /// ```
    #[must_use]
    pub fn with_level(mut self, level: LogLevel) -> Self {
        self.level = level;
        self
    }

    /// Gets the log file path.
    pub fn path(&self) -> &Path {
        &self.path
    }

    /// Gets the current log level.
    pub fn level(&self) -> LogLevel {
        self.level
    }

    /// Returns whether a message at `level` would be logged.
    pub fn enabled(&self, level: LogLevel) -> bool {
        level >= self.level
    }

    /// Attempts to log a message with the specified log level.
    /// Returns a Result indicating success or failure.
    fn try_log_line(&self, msg_level: LogLevel, message: &str) -> Result<()> {
        if !self.enabled(msg_level) {
            return Ok(());
        }

        let mut file = OpenOptions::new()
            .create(true)
            .append(true)
            .open(&self.path)
            .map_err(|e| {
                Error::new(
                    e.kind(),
                    format!("Failed to open log file {}: {}", self.path.display(), e),
                )
            })?;

        let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S");
        let log_entry = format!("[{}][{}] {}\n", timestamp, msg_level.as_str(), message);

        file.write_all(log_entry.as_bytes()).map_err(|e| {
            Error::new(
                e.kind(),
                format!("Failed to write to log file {}: {}", self.path.display(), e),
            )
        })?;

        Ok(())
    }

    /// Logs a message with the specified log level.
    /// Errors are printed to stderr.
    fn log_line(&self, msg_level: LogLevel, message: &str) {
        if let Err(e) = self.try_log_line(msg_level, message) {
            eprintln!("log-easy: {}", e);
        }
    }

    //---CONVENIENCE METHODS (Errors are printed to stderr)---
    /// These methods log messages at their respective levels.
    /// If an error occurs (e.g., file write failure), it is printed to stderr but not returned.
    pub fn trace(&self, msg: &str) {
        self.log_line(LogLevel::Trace, msg);
    }
    pub fn debug(&self, msg: &str) {
        self.log_line(LogLevel::Debug, msg);
    }
    pub fn info(&self, msg: &str) {
        self.log_line(LogLevel::Info, msg);
    }
    pub fn warn(&self, msg: &str) {
        self.log_line(LogLevel::Warn, msg);
    }
    pub fn error(&self, msg: &str) {
        self.log_line(LogLevel::Error, msg);
    }

    //---TRY CONVENIENCE METHODS (Returns error for user handling)---
    /// Fallible variants of the logging methods.
    ///
    /// Unlike `info()`/`warn()` etc., these return `std::io::Result<()>` so callers can handle failures.
    pub fn try_trace(&self, msg: &str) -> Result<()> {
        self.try_log_line(LogLevel::Trace, msg)
    }
    pub fn try_debug(&self, msg: &str) -> Result<()> {
        self.try_log_line(LogLevel::Debug, msg)
    }
    pub fn try_info(&self, msg: &str) -> Result<()> {
        self.try_log_line(LogLevel::Info, msg)
    }
    pub fn try_warn(&self, msg: &str) -> Result<()> {
        self.try_log_line(LogLevel::Warn, msg)
    }
    pub fn try_error(&self, msg: &str) -> Result<()> {
        self.try_log_line(LogLevel::Error, msg)
    }
}

//--- GLOBAL LOGGER INSTANCE ---

static GLOBAL_LOGGER: OnceLock<Logger> = OnceLock::new();

/// Initialize the global logger used by the `info!()` / `try_info!()` macros.
///
/// Call this once at program startup.
///
/// # Errors
/// Returns `AlreadyExists` if the global logger has already been initialized.
pub fn init<P: Into<PathBuf>>(path: P) -> Result<()> {
    GLOBAL_LOGGER.set(Logger::new(path)).map_err(|_| {
        Error::new(
            std::io::ErrorKind::AlreadyExists,
            "logger already initialized",
        )
    })?;
    Ok(())
}

/// Initialize the global logger with configured settings (e.g., log level).
/// Useful if you want to set a different log level for the global logger.
///
/// # Errors
/// Returns `AlreadyExists` if the global logger has already been initialized.
pub fn init_with(logger: Logger) -> Result<()> {
    GLOBAL_LOGGER.set(logger).map_err(|_| {
        Error::new(
            std::io::ErrorKind::AlreadyExists,
            "logger already initialized",
        )
    })?;
    Ok(())
}

/// Get a reference to the global logger (for macros).
pub(crate) fn global() -> Option<&'static Logger> {
    GLOBAL_LOGGER.get()
}
