//! `--mock <file>` canned-response loader.
//!
//! Mock file shape (JSON):
//! ```json
//! {
//!   "json": {
//!     "t1_add_three": { "1": { "content": "...", "usage": {...} } },
//!     "t2_length":    { "1": {...}, "2": {...} }
//!   },
//!   "ail": { ... }
//! }
//! ```
//! Where each `"<turn>"` entry has `"content"` (the program the
//! mocked model would emit) and `"usage"` (matching IONOS Usage shape).

use crate::ionos::{ChatResponse, Choice, ChoiceMessage, Usage};
use anyhow::{anyhow, Context, Result};
use serde::Deserialize;
use std::collections::BTreeMap;
use std::path::Path;

#[derive(Debug, Deserialize)]
struct MockTurn {
    content: String,
    usage: Usage,
}

#[derive(Debug, Deserialize)]
pub struct MockFile {
    /// cohort_name -> task_id -> turn -> response
    #[serde(flatten)]
    by_cohort: BTreeMap<String, BTreeMap<String, BTreeMap<String, MockTurn>>>,
}

pub struct MockResponses(MockFile);

impl MockResponses {
    pub fn load(path: &Path) -> Result<Self> {
        let text = std::fs::read_to_string(path)
            .with_context(|| format!("reading mock file {}", path.display()))?;
        let file: MockFile = serde_json::from_str(&text)
            .with_context(|| format!("parsing mock file {}", path.display()))?;
        Ok(MockResponses(file))
    }

    pub fn response_for(&self, cohort: &str, task: &str, turn: u32) -> Result<ChatResponse> {
        let turn_str = turn.to_string();
        let mt = self
            .0
            .by_cohort.get(cohort)
            .and_then(|t| t.get(task))
            .and_then(|t| t.get(&turn_str))
            .ok_or_else(|| anyhow!("mock has no entry for {cohort}/{task}/turn={turn}"))?;
        Ok(ChatResponse {
            choices: vec![Choice {
                message: ChoiceMessage { content: mt.content.clone() },
            }],
            usage: mt.usage,
        })
    }
}
