use std::io::Write as IoWrite;
use std::path::Path;
use std::time::Instant;

use crate::block::evasion;
use crate::fence::path::{check_path, extract_file_path};
use crate::policy::engine::evaluate;
use crate::snapshot::capture::capture_snapshot;
use crate::threat::classifier::{
    check_behavioral_evasion, classify_threat, extract_keywords, ThreatTier,
};
use crate::threat::killer::terminate_session;
use crate::threat::state::SessionState;
use crate::trace::logger::log_trace;
use crate::types::{Decision, HookInput, HookOutput, Policy, TraceEntry};

/// Handle a PreToolUse event.
/// This is the critical path — every tool call passes through here.
pub fn handle(input: &HookInput, policy: &Policy) -> HookOutput {
    let start = Instant::now();
    let tool_name = input.tool_name.as_deref().unwrap_or("unknown");
    let tool_input = input.tool_input.clone().unwrap_or_default();
    let cwd = Path::new(&input.cwd);

    // Load persistent session state
    let state_dir = cwd.join(".railyard/state");
    let mut state = SessionState::load(&state_dir, &input.session_id);
    state.increment_tool_call();

    // If session was previously terminated, block everything
    if state.terminated {
        let _ = state.save(&state_dir);
        return HookOutput::deny(
            "⛔ RAILYARD: Session terminated due to evasion detection. Start a new Claude Code session.",
        );
    }

    // Extract command for Bash tools
    let command = if tool_name == "Bash" {
        tool_input
            .get("command")
            .and_then(|v| v.as_str())
            .unwrap_or("")
    } else {
        ""
    };

    // === THREAT DETECTION (before policy evaluation) ===

    if tool_name == "Bash" && policy.mode == "hardcore" && !command.is_empty() {
        // Tier 3: Behavioral evasion (check BEFORE new blocks)
        if let Some(tier) = check_behavioral_evasion(&state, command) {
            let output = HookOutput::deny(&format!(
                "⛔ RAILYARD CRITICAL: Behavioral evasion detected. \
                 Retried blocked command with different syntax. \
                 Session terminated. Do not attempt any further tool calls."
            ));

            // Flush deny response before killing
            if let Ok(json) = serde_json::to_string(&output) {
                let _ = std::io::stdout().write_all(json.as_bytes());
                let _ = std::io::stdout().write_all(b"\n");
                let _ = std::io::stdout().flush();
            }

            let trace_dir = cwd.join(&policy.trace.directory);
            terminate_session(&mut state, &tier, command, &state_dir, &trace_dir);
            std::process::exit(0);
        }

        // Tier 1 & 2: Pattern-based evasion detection
        if let Some(tier) = classify_threat(command) {
            match &tier {
                ThreatTier::Tier1 { pattern } => {
                    // Immediate termination
                    let keywords = extract_keywords(command);
                    state.record_block(command, pattern, keywords, 1);

                    let output = HookOutput::deny(&format!(
                        "⛔ RAILYARD CRITICAL: Evasion pattern detected ({}). \
                         Session terminated. Do not attempt any further tool calls.",
                        pattern
                    ));

                    if let Ok(json) = serde_json::to_string(&output) {
                        let _ = std::io::stdout().write_all(json.as_bytes());
                        let _ = std::io::stdout().write_all(b"\n");
                        let _ = std::io::stdout().flush();
                    }

                    let trace_dir = cwd.join(&policy.trace.directory);
                    terminate_session(&mut state, &tier, command, &state_dir, &trace_dir);
                    std::process::exit(0);
                }

                ThreatTier::Tier2 { pattern } => {
                    if state.warning_count >= 1 {
                        // Second occurrence: terminate
                        let keywords = extract_keywords(command);
                        state.record_block(command, pattern, keywords, 2);

                        let output = HookOutput::deny(&format!(
                            "⛔ RAILYARD CRITICAL: Repeated suspicious pattern ({}). \
                             Session terminated. Do not attempt any further tool calls.",
                            pattern
                        ));

                        if let Ok(json) = serde_json::to_string(&output) {
                            let _ = std::io::stdout().write_all(json.as_bytes());
                            let _ = std::io::stdout().write_all(b"\n");
                            let _ = std::io::stdout().flush();
                        }

                        let trace_dir = cwd.join(&policy.trace.directory);
                        terminate_session(&mut state, &tier, command, &state_dir, &trace_dir);
                        std::process::exit(0);
                    } else {
                        // First occurrence: warn and continue to policy evaluation
                        state.record_warning();
                        log_decision(
                            input, policy, tool_name, &tool_input,
                            "warn", Some(&format!("tier2:{}", pattern)), start,
                        );
                    }
                }

                ThreatTier::Tier3 { .. } => {
                    // Tier 3 is handled above (behavioral check)
                    unreachable!();
                }
            }
        }
    }

    // === PATH FENCE ===

    if tool_name == "Bash" {
        if let Some(cmd) = tool_input.get("command").and_then(|v| v.as_str()) {
            let paths = evasion::extract_paths_from_command(cmd);
            for path in &paths {
                if let Err(reason) = check_path(&policy.fence, path, &input.cwd) {
                    let keywords = extract_keywords(cmd);
                    state.record_block(cmd, "path-fence", keywords, 0);
                    let _ = state.save(&state_dir);
                    log_decision(
                        input, policy, tool_name, &tool_input, "block",
                        Some("path-fence"), start,
                    );
                    return HookOutput::deny(&reason);
                }
            }
        }
    } else if let Some(file_path) = extract_file_path(tool_name, &tool_input) {
        if let Err(reason) = check_path(&policy.fence, &file_path, &input.cwd) {
            let _ = state.save(&state_dir);
            log_decision(
                input, policy, tool_name, &tool_input, "block",
                Some("path-fence"), start,
            );
            return HookOutput::deny(&reason);
        }
    }

    // === POLICY EVALUATION (allowlist → blocklist → approve) ===

    let decision = evaluate(policy, tool_name, &tool_input);

    match &decision {
        Decision::Allow => {
            // Snapshot before Write/Edit (if enabled)
            if policy.snapshot.enabled
                && policy.snapshot.tools.iter().any(|t| t == tool_name)
            {
                if let Some(file_path) = tool_input.get("file_path").and_then(|v| v.as_str()) {
                    let snap_dir = cwd.join(&policy.snapshot.directory);
                    let tool_use_id = input.tool_use_id.as_deref().unwrap_or("unknown");
                    if let Err(e) =
                        capture_snapshot(&snap_dir, &input.session_id, tool_use_id, file_path)
                    {
                        eprintln!("railyard: snapshot warning: {}", e);
                    }
                }
            }

            log_decision(input, policy, tool_name, &tool_input, "allow", None, start);
            let _ = state.save(&state_dir);
            HookOutput::allow()
        }
        Decision::Block { rule, message } => {
            // Record block for behavioral tracking (Tier 3)
            if tool_name == "Bash" && !command.is_empty() {
                let keywords = extract_keywords(command);
                state.record_block(command, rule, keywords, 0);
            }
            let _ = state.save(&state_dir);
            log_decision(
                input, policy, tool_name, &tool_input, "block", Some(rule), start,
            );
            HookOutput::deny(&format!("⛔ Railyard BLOCKED: {}", message))
        }
        Decision::Approve { rule, message } => {
            let _ = state.save(&state_dir);
            log_decision(
                input, policy, tool_name, &tool_input, "approve", Some(rule), start,
            );
            HookOutput::ask(&format!("⚠️ Railyard: {} — requires approval", message))
        }
    }
}

fn log_decision(
    input: &HookInput,
    policy: &Policy,
    tool_name: &str,
    tool_input: &serde_json::Value,
    decision: &str,
    rule: Option<&str>,
    start: Instant,
) {
    if !policy.trace.enabled {
        return;
    }

    let trace_dir = Path::new(&input.cwd).join(&policy.trace.directory);
    let input_summary = summarize_input(tool_name, tool_input);

    let entry = TraceEntry {
        timestamp: chrono::Utc::now().to_rfc3339(),
        session_id: input.session_id.clone(),
        event: "PreToolUse".to_string(),
        tool: tool_name.to_string(),
        input_summary,
        decision: decision.to_string(),
        rule: rule.map(|s| s.to_string()),
        duration_ms: start.elapsed().as_millis() as u64,
    };

    if let Err(e) = log_trace(&trace_dir, &input.session_id, &entry) {
        eprintln!("railyard: trace warning: {}", e);
    }
}

fn summarize_input(tool_name: &str, tool_input: &serde_json::Value) -> String {
    match tool_name {
        "Bash" => tool_input
            .get("command")
            .and_then(|v| v.as_str())
            .unwrap_or("(unknown command)")
            .chars()
            .take(200)
            .collect(),
        "Write" | "Edit" | "Read" => tool_input
            .get("file_path")
            .and_then(|v| v.as_str())
            .unwrap_or("(unknown path)")
            .to_string(),
        _ => serde_json::to_string(tool_input)
            .unwrap_or_default()
            .chars()
            .take(200)
            .collect(),
    }
}
