//! `[HH:MM:SS] <msg>` to stdout (flushed per line) and appended to the log file.
//!
//! The launcher pipes this process's stdout into its own log buffer and *parses*
//! some of these lines, so the per-line flush and the line shapes are a contract,
//! not cosmetics.

use std::fs::OpenOptions;
use std::io::{self, Write};
use std::path::PathBuf;

use crate::localtime;
use crate::procmem;

/// Environment override for the log file path.
pub const LOG_PATH_ENV: &str = "OPENFUT_AUTOPATCH_LOG";

pub struct Logger {
    path: PathBuf,
}

impl Logger {
    /// `$OPENFUT_AUTOPATCH_LOG`, defaulting to `/tmp/openfut-autopatch-<uid>.log`.
    ///
    /// Resolved once at startup, exactly like the Python's module-level `LOG`, so
    /// a later environment change cannot move the file mid-run.
    pub fn from_env() -> io::Result<Self> {
        let path = match std::env::var_os(LOG_PATH_ENV) {
            Some(path) if !path.is_empty() => PathBuf::from(path),
            _ => PathBuf::from(format!(
                "/tmp/openfut-autopatch-{}.log",
                procmem::current_uid()?
            )),
        };
        Ok(Self { path })
    }

    pub fn path(&self) -> &std::path::Path {
        &self.path
    }

    /// Emit one line. Timestamped in local time.
    pub fn log(&self, msg: &str) {
        let (h, m, s) = localtime::now_hms();
        let line = format!("[{h:02}:{m:02}:{s:02}] {msg}");

        let mut stdout = io::stdout().lock();
        // Ignore a broken pipe: the launcher may have stopped reading, and dying
        // here would leave FIFA's store patches unenforced.
        let _ = writeln!(stdout, "{line}");
        let _ = stdout.flush();
        drop(stdout);

        if let Err(e) = self.append(&line) {
            // The Python lets a failing log write kill the process. Patching the
            // running client matters more than the transcript, so report once to
            // stderr (the launcher captures it too) and carry on.
            let _ = writeln!(io::stderr(), "autopatch: cannot append to {}: {e}", self.path.display());
        }
    }

    fn append(&self, line: &str) -> io::Result<()> {
        let mut file = OpenOptions::new().create(true).append(true).open(&self.path)?;
        file.write_all(line.as_bytes())?;
        file.write_all(b"\n")
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn appends_a_timestamped_line_to_the_configured_path() {
        let path = std::env::temp_dir().join(format!(
            "openfut-autopatch-test-{}.log",
            std::process::id()
        ));
        let _ = std::fs::remove_file(&path);
        let logger = Logger {
            path: path.clone(),
        };
        logger.log("pid 4242: PATCHED cert gates");
        logger.log("second line");

        let body = std::fs::read_to_string(&path).unwrap();
        let lines: Vec<&str> = body.lines().collect();
        assert_eq!(lines.len(), 2);
        assert_eq!(&lines[0][..1], "[");
        assert_eq!(&lines[0][3..4], ":");
        assert_eq!(&lines[0][6..7], ":");
        assert_eq!(&lines[0][9..], "] pid 4242: PATCHED cert gates");
        assert!(lines[1].ends_with("] second line"));
        let _ = std::fs::remove_file(&path);
    }

    #[test]
    fn a_bad_log_path_does_not_kill_the_patcher() {
        let logger = Logger {
            path: PathBuf::from("/proc/definitely/not/writable.log"),
        };
        logger.log("still running");
    }
}
