use std::fs;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};

use serde_json::{Value, json};

const STATE_SCHEMA_VERSION: &str = "analyze_flow_state.v1";
const PENDING_RETRY_TTL_SECS: u64 = 10 * 60;
const RECENT_ANSWER_TTL_SECS: u64 = 2 * 60;

#[derive(Clone, Debug)]
pub(crate) struct PendingAnalyzeRetry {
    pub(crate) next_request: Value,
    pub(crate) command: Option<String>,
    pub(crate) reason: Option<String>,
    pub(crate) state_path: PathBuf,
}

#[derive(Clone, Debug)]
pub(crate) struct RecentAnalyzeAnswer {
    pub(crate) state_path: PathBuf,
}

#[derive(Clone, Debug)]
pub(crate) struct RecentAnalyzeReadForbidden {
    pub(crate) state_path: PathBuf,
    pub(crate) reason: Option<String>,
    pub(crate) next_action: Option<String>,
}

pub(crate) fn record_analyze_response(api_url: &str, body: &Value) {
    if let Some(next_request) = required_analyze_retry(body) {
        let payload = json!({
            "schema_version": STATE_SCHEMA_VERSION,
            "created_at_unix": now_unix_secs(),
            "api_url": api_url,
            "reason": next_request.get("reason").cloned().unwrap_or(Value::Null),
            "next_request": next_request,
        });
        let path = pending_retry_state_path(api_url);
        if let Some(parent) = path.parent() {
            let _ = fs::create_dir_all(parent);
        }
        let _ = fs::write(
            path,
            serde_json::to_vec_pretty(&payload).unwrap_or_default(),
        );
        clear_recent_analyze_answer(api_url);
        clear_recent_read_forbidden(api_url);
    } else if analyze_answer_complete(body) {
        clear_pending_analyze_retry(api_url);
        clear_recent_read_forbidden(api_url);
        let payload = json!({
            "schema_version": STATE_SCHEMA_VERSION,
            "created_at_unix": now_unix_secs(),
            "api_url": api_url,
            "answer_complete": true,
        });
        let path = recent_answer_state_path(api_url);
        if let Some(parent) = path.parent() {
            let _ = fs::create_dir_all(parent);
        }
        let _ = fs::write(
            path,
            serde_json::to_vec_pretty(&payload).unwrap_or_default(),
        );
    } else if let Some((reason, next_action)) = read_forbidden_non_answer(body) {
        clear_pending_analyze_retry(api_url);
        clear_recent_analyze_answer(api_url);
        let payload = json!({
            "schema_version": STATE_SCHEMA_VERSION,
            "created_at_unix": now_unix_secs(),
            "api_url": api_url,
            "reason": reason,
            "next_action": next_action,
        });
        let path = read_forbidden_state_path(api_url);
        if let Some(parent) = path.parent() {
            let _ = fs::create_dir_all(parent);
        }
        let _ = fs::write(
            path,
            serde_json::to_vec_pretty(&payload).unwrap_or_default(),
        );
    } else {
        clear_pending_analyze_retry(api_url);
        clear_recent_analyze_answer(api_url);
        clear_recent_read_forbidden(api_url);
    }
}

pub(crate) fn pending_analyze_retry(api_url: &str) -> Option<PendingAnalyzeRetry> {
    let path = pending_retry_state_path(api_url);
    let raw = fs::read_to_string(&path).ok()?;
    let payload = serde_json::from_str::<Value>(&raw).ok()?;

    if payload
        .get("schema_version")
        .and_then(Value::as_str)
        .is_some_and(|version| version != STATE_SCHEMA_VERSION)
    {
        let _ = fs::remove_file(path);
        return None;
    }

    let created_at = payload
        .get("created_at_unix")
        .and_then(Value::as_u64)
        .unwrap_or(0);
    if now_unix_secs().saturating_sub(created_at) > PENDING_RETRY_TTL_SECS {
        let _ = fs::remove_file(path);
        return None;
    }

    let next_request = payload.get("next_request")?.clone();
    if !is_required_retry_next_request(&next_request) {
        let _ = fs::remove_file(path);
        return None;
    }

    let command = retry_analyze_command(&next_request);
    let reason = payload
        .get("reason")
        .and_then(Value::as_str)
        .map(str::to_string);
    Some(PendingAnalyzeRetry {
        next_request,
        command,
        reason,
        state_path: path,
    })
}

pub(crate) fn clear_pending_analyze_retry(api_url: &str) {
    let _ = fs::remove_file(pending_retry_state_path(api_url));
}

pub(crate) fn pending_retry_block_payload(pending: &PendingAnalyzeRetry) -> Value {
    json!({
        "error": "analyze_retry_required",
        "message": "A previous analyze response returned a required analyze retry. Call that analyze retry before using read_query.",
        "field": "read_query",
        "docs_hint": "Run the command in command, or call kura analyze with details.next_request.request. This is a mechanical guard so analysis flows do not silently fall back to raw reads.",
        "next_action": "retry_analyze",
        "command": pending.command,
        "details": {
            "reason_code": "pending_analyze_retry",
            "reason": pending.reason,
            "state_path": pending.state_path,
            "next_request": pending.next_request,
            "must_call_before_read_query": true
        }
    })
}

pub(crate) fn recent_analyze_answer(api_url: &str) -> Option<RecentAnalyzeAnswer> {
    let path = recent_answer_state_path(api_url);
    let raw = fs::read_to_string(&path).ok()?;
    let payload = serde_json::from_str::<Value>(&raw).ok()?;

    if payload
        .get("schema_version")
        .and_then(Value::as_str)
        .is_some_and(|version| version != STATE_SCHEMA_VERSION)
    {
        let _ = fs::remove_file(path);
        return None;
    }

    let created_at = payload
        .get("created_at_unix")
        .and_then(Value::as_u64)
        .unwrap_or(0);
    if now_unix_secs().saturating_sub(created_at) > RECENT_ANSWER_TTL_SECS {
        let _ = fs::remove_file(path);
        return None;
    }

    if payload
        .get("answer_complete")
        .and_then(Value::as_bool)
        .unwrap_or(false)
    {
        Some(RecentAnalyzeAnswer { state_path: path })
    } else {
        let _ = fs::remove_file(path);
        None
    }
}

pub(crate) fn clear_recent_analyze_answer(api_url: &str) {
    let _ = fs::remove_file(recent_answer_state_path(api_url));
}

pub(crate) fn recent_read_forbidden(api_url: &str) -> Option<RecentAnalyzeReadForbidden> {
    let path = read_forbidden_state_path(api_url);
    let raw = fs::read_to_string(&path).ok()?;
    let payload = serde_json::from_str::<Value>(&raw).ok()?;

    if payload
        .get("schema_version")
        .and_then(Value::as_str)
        .is_some_and(|version| version != STATE_SCHEMA_VERSION)
    {
        let _ = fs::remove_file(path);
        return None;
    }

    let created_at = payload
        .get("created_at_unix")
        .and_then(Value::as_u64)
        .unwrap_or(0);
    if now_unix_secs().saturating_sub(created_at) > PENDING_RETRY_TTL_SECS {
        let _ = fs::remove_file(path);
        return None;
    }

    Some(RecentAnalyzeReadForbidden {
        reason: payload
            .get("reason")
            .and_then(Value::as_str)
            .map(str::to_string),
        next_action: payload
            .get("next_action")
            .and_then(Value::as_str)
            .map(str::to_string),
        state_path: path,
    })
}

pub(crate) fn clear_recent_read_forbidden(api_url: &str) {
    let _ = fs::remove_file(read_forbidden_state_path(api_url));
}

pub(crate) fn read_forbidden_block_payload(block: &RecentAnalyzeReadForbidden) -> Value {
    json!({
        "error": "analyze_read_query_forbidden",
        "message": "A previous analyze response explicitly forbids read_query for this analysis. Follow the analyze next_action instead of reconstructing the answer from raw reads.",
        "field": "read_query",
        "docs_hint": "Retry analyze, ask the user for the requested clarification, or start a genuinely new user request. Do not use read_query to continue the same analysis.",
        "next_action": block.next_action,
        "details": {
            "reason_code": "recent_analyze_read_query_forbidden",
            "reason": block.reason,
            "state_path": block.state_path,
        }
    })
}

pub(crate) fn recent_answer_block_payload(recent: &RecentAnalyzeAnswer) -> Value {
    json!({
        "error": "analyze_answer_already_complete",
        "message": "A previous analyze response already returned a complete answer. Write the final user answer from that analyze payload instead of continuing with read_query.",
        "field": "read_query",
        "docs_hint": "Use read_query only for a new user request, an explicit raw-record request, or a concrete follow-up requested by the analyze payload. For those cases pass --allow-after-analyze-answer.",
        "next_action": "write_final_answer",
        "details": {
            "reason_code": "recent_analyze_answer_complete",
            "state_path": recent.state_path,
            "read_query_allowed_override": "--allow-after-analyze-answer"
        }
    })
}

fn required_analyze_retry(body: &Value) -> Option<Value> {
    let next_request = body
        .get("next_request")
        .or_else(|| body.pointer("/suggested_next_action/next_request"))?;
    if is_required_retry_next_request(next_request) {
        Some(next_request.clone())
    } else {
        None
    }
}

fn analyze_answer_complete(body: &Value) -> bool {
    body.get("type").and_then(Value::as_str) == Some("answer")
        && body
            .pointer("/agent_usage/answer_complete")
            .and_then(Value::as_bool)
            .unwrap_or(false)
}

fn read_forbidden_non_answer(body: &Value) -> Option<(Option<String>, Option<String>)> {
    if body.get("type").and_then(Value::as_str) == Some("answer") {
        return None;
    }
    let agent_usage = body.get("agent_usage")?;
    let must_not_read = agent_usage
        .get("must_not_call_read_query_for_same_question")
        .and_then(Value::as_bool)
        .unwrap_or(false);
    let read_allowed = agent_usage
        .get("read_query_allowed")
        .and_then(Value::as_bool)
        .unwrap_or(false);
    if !must_not_read || read_allowed {
        return None;
    }
    let reason = body
        .get("reason")
        .or_else(|| body.get("reason_code"))
        .and_then(Value::as_str)
        .map(str::to_string);
    let next_action = agent_usage
        .get("next_action")
        .and_then(Value::as_str)
        .map(str::to_string);
    Some((reason, next_action))
}

fn is_required_retry_next_request(next_request: &Value) -> bool {
    next_request
        .get("action")
        .and_then(Value::as_str)
        .is_some_and(|action| action == "retry_analyze")
        && next_request
            .get("must_call_before_read_query")
            .and_then(Value::as_bool)
            .unwrap_or(false)
}

fn retry_analyze_command(next_request: &Value) -> Option<String> {
    let request = next_request
        .get("request")
        .and_then(public_retry_request_from_value)
        .or_else(|| next_request.get("intent").cloned())?;
    let request_json = serde_json::to_string(&request).ok()?;
    Some(format!(
        "kura analyze --request-json {}",
        shell_single_quote(&request_json)
    ))
}

fn public_retry_request_from_value(value: &Value) -> Option<Value> {
    if let Some(intent) = value.get("intent") {
        return Some(intent.clone());
    }
    Some(value.clone())
}

fn shell_single_quote(value: &str) -> String {
    format!("'{}'", value.replace('\'', "'\"'\"'"))
}

fn now_unix_secs() -> u64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|duration| duration.as_secs())
        .unwrap_or(0)
}

fn pending_retry_state_path(api_url: &str) -> PathBuf {
    state_path(api_url)
}

fn recent_answer_state_path(api_url: &str) -> PathBuf {
    let base = state_path(api_url);
    let file_name = base
        .file_name()
        .and_then(|name| name.to_str())
        .unwrap_or("state.json");
    base.with_file_name(format!("{file_name}.answer.json"))
}

fn read_forbidden_state_path(api_url: &str) -> PathBuf {
    let base = state_path(api_url);
    let file_name = base
        .file_name()
        .and_then(|name| name.to_str())
        .unwrap_or("state.json");
    base.with_file_name(format!("{file_name}.read-forbidden.json"))
}

fn state_path(api_url: &str) -> PathBuf {
    if let Ok(path) = std::env::var("KURA_ANALYZE_FLOW_STATE_FILE") {
        let trimmed = path.trim();
        if !trimmed.is_empty() {
            return PathBuf::from(trimmed);
        }
    }

    let cwd = std::env::current_dir()
        .ok()
        .map(|path| path.display().to_string())
        .unwrap_or_else(|| "unknown-cwd".to_string());
    let key = sanitize_state_key(&format!("{api_url}|{cwd}"));
    std::env::temp_dir()
        .join("kura-analyze-flow")
        .join(format!("{key}.json"))
}

fn sanitize_state_key(raw: &str) -> String {
    let mut out = String::with_capacity(raw.len().min(160));
    for ch in raw.chars() {
        if ch.is_ascii_alphanumeric() {
            out.push(ch.to_ascii_lowercase());
        } else {
            out.push('_');
        }
        if out.len() >= 160 {
            break;
        }
    }
    while out.contains("__") {
        out = out.replace("__", "_");
    }
    out.trim_matches('_').to_string()
}

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

    fn with_unique_api_url(test_name: &str, f: impl FnOnce(&str)) {
        let api_url = format!("http://example.test/{test_name}/{}", uuid::Uuid::now_v7());
        clear_pending_analyze_retry(&api_url);
        clear_recent_analyze_answer(&api_url);
        clear_recent_read_forbidden(&api_url);
        f(&api_url);
        clear_pending_analyze_retry(&api_url);
        clear_recent_analyze_answer(&api_url);
        clear_recent_read_forbidden(&api_url);
    }

    #[test]
    fn records_required_retry_and_blocks_with_command() {
        with_unique_api_url("pending-retry", |api_url| {
            record_analyze_response(
                api_url,
                &json!({
                    "type": "refusal",
                    "next_request": {
                        "action": "retry_analyze",
                        "must_call_before_read_query": true,
                        "intent": {
                            "question_type": "trend",
                            "entity": {"entity_id": "hang_muscle_snatch_bk"},
                            "outcome": {"signal": "estimated_1rm"}
                        }
                    }
                }),
            );

            let pending = pending_analyze_retry(api_url).expect("pending retry");
            assert!(
                pending
                    .command
                    .as_deref()
                    .unwrap()
                    .contains("hang_muscle_snatch_bk")
            );
            let block = pending_retry_block_payload(&pending);
            assert_eq!(block["error"], json!("analyze_retry_required"));
            assert_eq!(block["details"]["must_call_before_read_query"], json!(true));
        });
    }

    #[test]
    fn clears_pending_retry_after_non_retry_analyze_response() {
        with_unique_api_url("clear-retry", |api_url| {
            record_analyze_response(
                api_url,
                &json!({
                    "type": "refusal",
                    "next_request": {
                        "action": "retry_analyze",
                        "must_call_before_read_query": true,
                        "intent": {"question_type": "trend"}
                    }
                }),
            );
            assert!(pending_analyze_retry(api_url).is_some());

            record_analyze_response(api_url, &json!({"type": "answer"}));

            assert!(pending_analyze_retry(api_url).is_none());
        });
    }

    #[test]
    fn records_terminal_answer_and_blocks_follow_up_reads() {
        with_unique_api_url("terminal-answer", |api_url| {
            record_analyze_response(
                api_url,
                &json!({
                    "type": "answer",
                    "agent_usage": {"answer_complete": true}
                }),
            );

            let recent = recent_analyze_answer(api_url).expect("recent answer");
            let block = recent_answer_block_payload(&recent);
            assert_eq!(block["error"], json!("analyze_answer_already_complete"));
            assert_eq!(block["next_action"], json!("write_final_answer"));
        });
    }

    #[test]
    fn clears_recent_answer_after_non_terminal_analyze_response() {
        with_unique_api_url("clear-terminal-answer", |api_url| {
            record_analyze_response(
                api_url,
                &json!({
                    "type": "answer",
                    "agent_usage": {"answer_complete": true}
                }),
            );
            assert!(recent_analyze_answer(api_url).is_some());

            record_analyze_response(api_url, &json!({"type": "clarification"}));

            assert!(recent_analyze_answer(api_url).is_none());
        });
    }

    #[test]
    fn records_non_answer_read_forbidden_guard() {
        with_unique_api_url("read-forbidden", |api_url| {
            record_analyze_response(
                api_url,
                &json!({
                    "type": "unsupported",
                    "reason": "driver signal \"knee_pain\" is not supported by analyze",
                    "agent_usage": {
                        "answer_complete": false,
                        "read_query_allowed": false,
                        "must_not_call_read_query_for_same_question": true,
                        "next_action": "retry_analyze_with_suggested_request"
                    }
                }),
            );

            let block = recent_read_forbidden(api_url).expect("read forbidden guard");
            let payload = read_forbidden_block_payload(&block);
            assert_eq!(payload["error"], json!("analyze_read_query_forbidden"));
            assert_eq!(
                payload["next_action"],
                json!("retry_analyze_with_suggested_request")
            );
        });
    }
}
