//! Shared helpers for CLI integration tests.
//!
//! `TestVault::init()` spins up a fresh vault in a `TempDir` using
//! `RELICARIO_TEST_PASSPHRASE` as the escape hatch (bypasses TTY prompts).
//! Every `run()` / `run_with_input()` call sets both `RELICARIO_IMAGE` and
//! `RELICARIO_TEST_PASSPHRASE`, so vault-mutating commands unlock without
//! interactive input.
//!
//! Note for Task 23 implementers: commands that prompt for a *new item
//! password* (i.e. `edit` when changing a Login password) also use
//! `rpassword`. Plumb `RELICARIO_TEST_ITEM_PASSWORD` through `cmd_edit` in
//! main.rs, or use an item type / edit path that avoids the rpassword call.

use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};

use assert_cmd::cargo::CommandCargoExt;
use tempfile::TempDir;

pub struct TestVault {
    pub dir: TempDir,
    pub reference_image: PathBuf,
    pub passphrase: String,
}

impl TestVault {
    pub fn init() -> Self {
        let dir = TempDir::new().expect("tempdir");
        let carrier = make_test_jpeg(400, 300);
        let carrier_path = dir.path().join("carrier.jpg");
        std::fs::write(&carrier_path, &carrier).unwrap();

        let passphrase = "correct horse battery staple 2026".to_string();
        let ref_path = dir.path().join("reference.jpg");

        let mut cmd = Command::cargo_bin("relicario").unwrap();
        cmd.current_dir(dir.path())
            .env("RELICARIO_TEST_PASSPHRASE", &passphrase)
            .args([
                "init",
                "--image",
                carrier_path.to_str().unwrap(),
                "--output",
                ref_path.to_str().unwrap(),
            ])
            .stdin(Stdio::null())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped());
        let out = cmd.output().unwrap();
        assert!(
            out.status.success(),
            "init failed:\nstdout: {}\nstderr: {}",
            String::from_utf8_lossy(&out.stdout),
            String::from_utf8_lossy(&out.stderr)
        );

        Self {
            dir,
            reference_image: ref_path,
            passphrase,
        }
    }

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

    pub fn run(&self, args: &[&str]) -> std::process::Output {
        let mut cmd = Command::cargo_bin("relicario").unwrap();
        cmd.current_dir(self.dir.path())
            .env("RELICARIO_IMAGE", &self.reference_image)
            .env("RELICARIO_TEST_PASSPHRASE", &self.passphrase)
            .args(args)
            .stdin(Stdio::null())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped());
        cmd.output().unwrap()
    }

    #[allow(dead_code)]
    pub fn run_with_backup_pass(&self, args: &[&str], backup_pass: &str) -> std::process::Output {
        let mut cmd = Command::cargo_bin("relicario").unwrap();
        cmd.current_dir(self.dir.path())
            .env("RELICARIO_IMAGE", &self.reference_image)
            .env("RELICARIO_TEST_PASSPHRASE", &self.passphrase)
            .env("RELICARIO_TEST_BACKUP_PASSPHRASE", backup_pass)
            .args(args)
            .stdin(Stdio::null())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped());
        cmd.output().unwrap()
    }

    #[allow(dead_code)]
    pub fn run_with_input(&self, args: &[&str], extra: &[&str]) -> std::process::Output {
        let mut cmd = Command::cargo_bin("relicario").unwrap();
        cmd.current_dir(self.dir.path())
            .env("RELICARIO_IMAGE", &self.reference_image)
            .env("RELICARIO_TEST_PASSPHRASE", &self.passphrase)
            .args(args)
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped());
        let mut child = cmd.spawn().unwrap();
        {
            let stdin = child.stdin.as_mut().unwrap();
            for line in extra {
                writeln!(stdin, "{line}").unwrap();
            }
        }
        child.wait_with_output().unwrap()
    }
}

pub fn make_test_jpeg(w: u32, h: u32) -> Vec<u8> {
    use image::codecs::jpeg::JpegEncoder;
    use image::{ExtendedColorType, ImageBuffer, ImageEncoder, Rgb};

    let img = ImageBuffer::from_fn(w, h, |x, y| {
        Rgb([
            ((x * 7 + y * 13) % 256) as u8,
            ((x * 11 + y * 3) % 256) as u8,
            ((x * 5 + y * 17) % 256) as u8,
        ])
    });
    let mut out = Vec::new();
    JpegEncoder::new_with_quality(&mut out, 92)
        .write_image(img.as_raw(), w, h, ExtendedColorType::Rgb8)
        .unwrap();
    out
}
