//! T6 integration tests for [`ClaudeTranscriptInspector`] against a real temp
//! directory and a real [`LocalFileSystem`], exercising the full transcript
//! discovery + parsing path:
//!
//! - a realistic `~/.claude/projects/<encoded-cwd>/<id>.jsonl` fixture →
//!   `last_topic` + `token_count` extracted;
//! - a missing transcript → [`InspectError::NotFound`];
//! - malformed lines in the middle → skipped, valid lines still extracted;
//! - [`SessionInspector::supports`] true for a `CLAUDE.md` profile, false for an
//!   `AGENTS.md` one or a non-`conventionFile` injection.

use std::path::PathBuf;
use std::sync::Arc;

use domain::ids::ProfileId;
use domain::ports::{FileSystem, InspectError, SessionInspector};
use domain::profile::{AgentProfile, ContextInjection};
use domain::project::ProjectPath;
use infrastructure::{ClaudeTranscriptInspector, LocalFileSystem};
use uuid::Uuid;

/// A unique scratch directory under the OS temp dir, cleaned up on drop. Plays
/// the role of the user's `$HOME` so the inspector reads
/// `<home>/.claude/projects/...` entirely inside the fixture.
struct TempHome(PathBuf);
impl TempHome {
    fn new() -> Self {
        let p = std::env::temp_dir().join(format!("idea-t6-claude-{}", Uuid::new_v4()));
        std::fs::create_dir_all(&p).unwrap();
        Self(p)
    }
    fn path(&self) -> String {
        self.0.to_string_lossy().into_owned()
    }
    /// Writes a transcript at `<home>/.claude/projects/<encoded-cwd>/<id>.jsonl`,
    /// mirroring the adapter's own cwd-encoding convention.
    fn write_transcript(&self, cwd: &str, conversation_id: &str, body: &str) {
        let encoded: String = cwd
            .chars()
            .map(|c| if c == '/' || c == '\\' { '-' } else { c })
            .collect();
        let dir = self.0.join(".claude").join("projects").join(&encoded);
        std::fs::create_dir_all(&dir).unwrap();
        std::fs::write(dir.join(format!("{conversation_id}.jsonl")), body).unwrap();
    }
}
impl Drop for TempHome {
    fn drop(&mut self) {
        let _ = std::fs::remove_dir_all(&self.0);
    }
}

fn inspector(home: &TempHome) -> ClaudeTranscriptInspector {
    let fs: Arc<dyn FileSystem> = Arc::new(LocalFileSystem::new());
    ClaudeTranscriptInspector::with_base_dir(fs, home.path())
}

fn claude_profile() -> AgentProfile {
    AgentProfile::new(
        ProfileId::from_uuid(Uuid::from_u128(1)),
        "Claude Code",
        "claude",
        Vec::new(),
        ContextInjection::convention_file("CLAUDE.md").unwrap(),
        Some("claude --version".to_owned()),
        "{agentRunDir}",
        None,
    )
    .unwrap()
}

#[tokio::test]
async fn extracts_last_topic_and_token_count_from_fixture() {
    let home = TempHome::new();
    let cwd = "/home/dev/Projects/Demo";
    let id = "11111111-2222-3333-4444-555555555555";
    let body = concat!(
        r#"{"role":"user","content":"set up the project"}"#,
        "\n",
        r#"{"role":"assistant","content":"on it","usage":{"input_tokens":12,"output_tokens":8}}"#,
        "\n",
        r#"{"role":"user","content":"now add tests"}"#,
        "\n",
        r#"{"role":"assistant","content":"done","usage":{"input_tokens":30,"output_tokens":20}}"#,
        "\n",
    );
    home.write_transcript(cwd, id, body);

    let insp = inspector(&home);
    let details = insp
        .details(&claude_profile(), id, &ProjectPath::new(cwd).unwrap())
        .await
        .expect("transcript should be found and parsed");

    assert_eq!(details.last_topic.as_deref(), Some("now add tests"));
    assert_eq!(details.token_count, Some(70));
}

#[tokio::test]
async fn missing_transcript_is_not_found() {
    let home = TempHome::new();
    let insp = inspector(&home);
    let err = insp
        .details(
            &claude_profile(),
            "does-not-exist",
            &ProjectPath::new("/home/dev/nope").unwrap(),
        )
        .await
        .expect_err("absent transcript must error");
    assert_eq!(err, InspectError::NotFound);
}

#[tokio::test]
async fn malformed_lines_are_skipped_not_fatal() {
    let home = TempHome::new();
    let cwd = "/home/dev/Projects/Resilient";
    let id = "aaaa";
    let body = concat!(
        r#"{"role":"user","content":"valid first"}"#,
        "\n",
        "{ this is not valid json",
        "\n",
        "plain garbage line",
        "\n",
        r#"{"role":"assistant","usage":{"input_tokens":5,"output_tokens":6}}"#,
        "\n",
        r#"{"role":"user","content":"valid last"}"#,
        "\n",
    );
    home.write_transcript(cwd, id, body);

    let insp = inspector(&home);
    let details = insp
        .details(&claude_profile(), id, &ProjectPath::new(cwd).unwrap())
        .await
        .expect("readable transcript with bad lines should still parse");

    assert_eq!(details.last_topic.as_deref(), Some("valid last"));
    assert_eq!(details.token_count, Some(11));
}

#[test]
fn supports_only_claude_md_profiles() {
    let fs: Arc<dyn FileSystem> = Arc::new(LocalFileSystem::new());
    let insp = ClaudeTranscriptInspector::new(fs, "/tmp/whatever");

    // CLAUDE.md → supported.
    assert!(insp.supports(&claude_profile()));

    // AGENTS.md (Codex) → not supported.
    let codex = AgentProfile::new(
        ProfileId::from_uuid(Uuid::from_u128(2)),
        "Codex",
        "codex",
        Vec::new(),
        ContextInjection::convention_file("AGENTS.md").unwrap(),
        None,
        "{agentRunDir}",
        None,
    )
    .unwrap();
    assert!(!insp.supports(&codex));

    // Non-conventionFile injection (stdin) → not supported.
    let stdin_profile = AgentProfile::new(
        ProfileId::from_uuid(Uuid::from_u128(3)),
        "Piped",
        "aider",
        Vec::new(),
        ContextInjection::stdin(),
        None,
        "{agentRunDir}",
        None,
    )
    .unwrap();
    assert!(!insp.supports(&stdin_profile));
}
