use super::*;
use codex_execpolicy::Decision;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_absolute_path::test_support::test_path_buf;
use pretty_assertions::assert_eq;
use std::path::PathBuf;

#[test]
fn renders_sandbox_mode_text() {
    assert_eq!(
        sandbox_text(SandboxMode::WorkspaceWrite, NetworkAccess::Restricted),
        "Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `workspace-write`: The sandbox permits reading files, and editing files in `cwd` and `writable_roots`. Editing files in other directories requires approval. Network access is restricted."
    );

    assert_eq!(
        sandbox_text(SandboxMode::ReadOnly, NetworkAccess::Restricted),
        "Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `read-only`: The sandbox only permits reading files. Network access is restricted."
    );

    assert_eq!(
        sandbox_text(SandboxMode::DangerFullAccess, NetworkAccess::Enabled),
        "Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `danger-full-access`: No filesystem sandboxing - all commands are permitted. Network access is enabled."
    );
}

#[test]
fn builds_permissions_with_network_access_override() {
    let instructions = PermissionsInstructions::from_permissions_with_network(
        SandboxMode::WorkspaceWrite,
        NetworkAccess::Enabled,
        PermissionsPromptConfig {
            approval_policy: AskForApproval::OnRequest,
            approvals_reviewer: ApprovalsReviewer::User,
            exec_policy: &Policy::empty(),
            exec_permission_approvals_enabled: false,
            request_permissions_tool_enabled: false,
        },
        /*writable_roots*/ None,
    );

    let text = instructions.body();
    assert!(
        text.contains("Network access is enabled."),
        "expected network access to be enabled in message"
    );
    assert!(
        text.contains("How to request escalation"),
        "expected approval guidance to be included"
    );
}

#[test]
fn builds_permissions_from_profile() {
    let cwd = PathBuf::from("/tmp");
    let writable_root =
        AbsolutePathBuf::from_absolute_path(cwd.join("repo")).expect("absolute path");
    let permission_profile = PermissionProfile::from_runtime_permissions(
        &FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
            path: FileSystemPath::Path {
                path: writable_root.clone(),
            },
            access: FileSystemAccessMode::Write,
        }]),
        NetworkSandboxPolicy::Enabled,
    );

    let instructions = PermissionsInstructions::from_permission_profile(
        &permission_profile,
        AskForApproval::UnlessTrusted,
        ApprovalsReviewer::User,
        &Policy::empty(),
        &cwd,
        /*exec_permission_approvals_enabled*/ false,
        /*request_permissions_tool_enabled*/ false,
    );
    let text = instructions.body();
    assert!(text.contains("`sandbox_mode` is `workspace-write`"));
    assert!(text.contains("Network access is enabled."));
    assert!(text.contains(writable_root.to_string_lossy().as_ref()));
}

#[test]
fn builds_permissions_from_profile_with_denied_reads() {
    let cwd = test_path_buf("/tmp");
    let denied_root =
        AbsolutePathBuf::from_absolute_path(cwd.join("blocked")).expect("absolute path");
    let denied_glob = cwd.join("blocked").join("**");
    let permission_profile = PermissionProfile::from_runtime_permissions(
        &FileSystemSandboxPolicy::restricted(vec![
            FileSystemSandboxEntry {
                path: FileSystemPath::Special {
                    value: codex_protocol::permissions::FileSystemSpecialPath::Root,
                },
                access: FileSystemAccessMode::Read,
            },
            FileSystemSandboxEntry {
                path: FileSystemPath::Path {
                    path: denied_root.clone(),
                },
                access: FileSystemAccessMode::Deny,
            },
            FileSystemSandboxEntry {
                path: FileSystemPath::GlobPattern {
                    pattern: denied_glob.to_string_lossy().into_owned(),
                },
                access: FileSystemAccessMode::Deny,
            },
        ]),
        NetworkSandboxPolicy::Restricted,
    );

    let instructions = PermissionsInstructions::from_permission_profile(
        &permission_profile,
        AskForApproval::OnRequest,
        ApprovalsReviewer::AutoReview,
        &Policy::empty(),
        &cwd,
        /*exec_permission_approvals_enabled*/ false,
        /*request_permissions_tool_enabled*/ false,
    );
    let text = instructions.body();
    assert!(text.contains("## Denied filesystem reads"));
    assert!(text.contains("Do not request escalation or additional permissions"));
    assert!(text.contains(denied_root.to_string_lossy().as_ref()));
    assert!(text.contains(&format!("glob `{}`", denied_glob.to_string_lossy())));
}

#[test]
fn includes_request_rule_instructions_for_on_request() {
    let mut exec_policy = Policy::empty();
    exec_policy
        .add_prefix_rule(&["git".to_string(), "pull".to_string()], Decision::Allow)
        .expect("add rule");
    let instructions = PermissionsInstructions::from_permissions_with_network(
        SandboxMode::WorkspaceWrite,
        NetworkAccess::Enabled,
        PermissionsPromptConfig {
            approval_policy: AskForApproval::OnRequest,
            approvals_reviewer: ApprovalsReviewer::User,
            exec_policy: &exec_policy,
            exec_permission_approvals_enabled: false,
            request_permissions_tool_enabled: false,
        },
        /*writable_roots*/ None,
    );

    let text = instructions.body();
    assert!(text.contains("prefix_rule"));
    assert!(text.contains("Approved command prefixes"));
    assert!(text.contains(r#"["git", "pull"]"#));
}

#[test]
fn includes_request_permissions_tool_instructions_for_unless_trusted_when_enabled() {
    let instructions = PermissionsInstructions::from_permissions_with_network(
        SandboxMode::WorkspaceWrite,
        NetworkAccess::Enabled,
        PermissionsPromptConfig {
            approval_policy: AskForApproval::UnlessTrusted,
            approvals_reviewer: ApprovalsReviewer::User,
            exec_policy: &Policy::empty(),
            exec_permission_approvals_enabled: false,
            request_permissions_tool_enabled: true,
        },
        /*writable_roots*/ None,
    );

    let text = instructions.body();
    assert!(text.contains("`approval_policy` is `unless-trusted`"));
    assert!(text.contains("# request_permissions Tool"));
}

#[test]
fn includes_request_permissions_tool_instructions_for_on_failure_when_enabled() {
    let instructions = PermissionsInstructions::from_permissions_with_network(
        SandboxMode::WorkspaceWrite,
        NetworkAccess::Enabled,
        PermissionsPromptConfig {
            approval_policy: AskForApproval::OnFailure,
            approvals_reviewer: ApprovalsReviewer::User,
            exec_policy: &Policy::empty(),
            exec_permission_approvals_enabled: false,
            request_permissions_tool_enabled: true,
        },
        /*writable_roots*/ None,
    );

    let text = instructions.body();
    assert!(text.contains("`approval_policy` is `on-failure`"));
    assert!(text.contains("# request_permissions Tool"));
}

#[test]
fn includes_request_permission_rule_instructions_for_on_request_when_enabled() {
    let instructions = PermissionsInstructions::from_permissions_with_network(
        SandboxMode::WorkspaceWrite,
        NetworkAccess::Enabled,
        PermissionsPromptConfig {
            approval_policy: AskForApproval::OnRequest,
            approvals_reviewer: ApprovalsReviewer::User,
            exec_policy: &Policy::empty(),
            exec_permission_approvals_enabled: true,
            request_permissions_tool_enabled: false,
        },
        /*writable_roots*/ None,
    );

    let text = instructions.body();
    assert!(text.contains("with_additional_permissions"));
    assert!(text.contains("additional_permissions"));
}

#[test]
fn includes_request_permissions_tool_instructions_for_on_request_when_tool_is_enabled() {
    let instructions = PermissionsInstructions::from_permissions_with_network(
        SandboxMode::WorkspaceWrite,
        NetworkAccess::Enabled,
        PermissionsPromptConfig {
            approval_policy: AskForApproval::OnRequest,
            approvals_reviewer: ApprovalsReviewer::User,
            exec_policy: &Policy::empty(),
            exec_permission_approvals_enabled: false,
            request_permissions_tool_enabled: true,
        },
        /*writable_roots*/ None,
    );

    let text = instructions.body();
    assert!(text.contains("# request_permissions Tool"));
    assert!(text.contains("The built-in `request_permissions` tool is available in this session."));
}

#[test]
fn on_request_includes_tool_guidance_alongside_inline_permission_guidance_when_both_exist() {
    let instructions = PermissionsInstructions::from_permissions_with_network(
        SandboxMode::WorkspaceWrite,
        NetworkAccess::Enabled,
        PermissionsPromptConfig {
            approval_policy: AskForApproval::OnRequest,
            approvals_reviewer: ApprovalsReviewer::User,
            exec_policy: &Policy::empty(),
            exec_permission_approvals_enabled: true,
            request_permissions_tool_enabled: true,
        },
        /*writable_roots*/ None,
    );

    let text = instructions.body();
    assert!(text.contains("with_additional_permissions"));
    assert!(text.contains("# request_permissions Tool"));
}

#[test]
fn auto_review_approvals_append_auto_review_specific_guidance() {
    let text = approval_text(
        AskForApproval::OnRequest,
        ApprovalsReviewer::AutoReview,
        &Policy::empty(),
        /*exec_permission_approvals_enabled*/ false,
        /*request_permissions_tool_enabled*/ false,
    );

    assert!(text.contains("`approvals_reviewer` is `auto_review`"));
    assert!(!text.contains("`approvals_reviewer` is `guardian_subagent`"));
    assert!(text.contains("materially safer alternative"));
}

#[test]
fn auto_review_approvals_omit_auto_review_specific_guidance_when_approval_is_never() {
    let text = approval_text(
        AskForApproval::Never,
        ApprovalsReviewer::AutoReview,
        &Policy::empty(),
        /*exec_permission_approvals_enabled*/ false,
        /*request_permissions_tool_enabled*/ false,
    );

    assert!(!text.contains("`approvals_reviewer` is `auto_review`"));
    assert!(!text.contains("`approvals_reviewer` is `guardian_subagent`"));
}

fn granular_categories_section(title: &str, categories: &[&str]) -> String {
    format!("{title}\n{}", categories.join("\n"))
}

fn granular_prompt_expected(
    prompted_categories: &[&str],
    rejected_categories: &[&str],
    include_shell_permission_request_instructions: bool,
    include_request_permissions_tool_section: bool,
) -> String {
    let mut sections = vec![granular_prompt_intro_text().to_string()];
    if !prompted_categories.is_empty() {
        sections.push(granular_categories_section(
            "These approval categories may still prompt the user when needed:",
            prompted_categories,
        ));
    }
    if !rejected_categories.is_empty() {
        sections.push(granular_categories_section(
            "These approval categories are automatically rejected instead of prompting the user:",
            rejected_categories,
        ));
    }
    if include_shell_permission_request_instructions {
        sections.push(APPROVAL_POLICY_ON_REQUEST_RULE_REQUEST_PERMISSION.to_string());
    }
    if include_request_permissions_tool_section {
        sections.push(request_permissions_tool_prompt_section().to_string());
    }
    sections.join("\n\n")
}

#[test]
fn granular_policy_lists_prompted_and_rejected_categories_separately() {
    let text = approval_text(
        AskForApproval::Granular(GranularApprovalConfig {
            sandbox_approval: false,
            rules: true,
            skill_approval: false,
            request_permissions: true,
            mcp_elicitations: false,
        }),
        ApprovalsReviewer::User,
        &Policy::empty(),
        /*exec_permission_approvals_enabled*/ true,
        /*request_permissions_tool_enabled*/ false,
    );

    assert_eq!(
        text,
        [
            granular_prompt_intro_text().to_string(),
            granular_categories_section(
                "These approval categories may still prompt the user when needed:",
                &["- `rules`"],
            ),
            granular_categories_section(
                "These approval categories are automatically rejected instead of prompting the user:",
                &[
                    "- `sandbox_approval`",
                    "- `skill_approval`",
                    "- `mcp_elicitations`",
                ],
            ),
        ]
        .join("\n\n")
    );
}

#[test]
fn granular_policy_includes_command_permission_instructions_when_sandbox_approval_can_prompt() {
    let text = approval_text(
        AskForApproval::Granular(GranularApprovalConfig {
            sandbox_approval: true,
            rules: true,
            skill_approval: true,
            request_permissions: true,
            mcp_elicitations: true,
        }),
        ApprovalsReviewer::User,
        &Policy::empty(),
        /*exec_permission_approvals_enabled*/ true,
        /*request_permissions_tool_enabled*/ false,
    );

    assert_eq!(
        text,
        granular_prompt_expected(
            &[
                "- `sandbox_approval`",
                "- `rules`",
                "- `skill_approval`",
                "- `mcp_elicitations`",
            ],
            &[],
            /*include_shell_permission_request_instructions*/ true,
            /*include_request_permissions_tool_section*/ false,
        )
    );
}

#[test]
fn granular_policy_omits_shell_permission_instructions_when_inline_requests_are_disabled() {
    let text = approval_text(
        AskForApproval::Granular(GranularApprovalConfig {
            sandbox_approval: true,
            rules: true,
            skill_approval: true,
            request_permissions: true,
            mcp_elicitations: true,
        }),
        ApprovalsReviewer::User,
        &Policy::empty(),
        /*exec_permission_approvals_enabled*/ false,
        /*request_permissions_tool_enabled*/ false,
    );

    assert_eq!(
        text,
        granular_prompt_expected(
            &[
                "- `sandbox_approval`",
                "- `rules`",
                "- `skill_approval`",
                "- `mcp_elicitations`",
            ],
            &[],
            /*include_shell_permission_request_instructions*/ false,
            /*include_request_permissions_tool_section*/ false,
        )
    );
}

#[test]
fn granular_policy_includes_request_permissions_tool_only_when_that_prompt_can_still_fire() {
    let allowed = approval_text(
        AskForApproval::Granular(GranularApprovalConfig {
            sandbox_approval: true,
            rules: true,
            skill_approval: true,
            request_permissions: true,
            mcp_elicitations: true,
        }),
        ApprovalsReviewer::User,
        &Policy::empty(),
        /*exec_permission_approvals_enabled*/ true,
        /*request_permissions_tool_enabled*/ true,
    );
    assert!(allowed.contains("# request_permissions Tool"));

    let rejected = approval_text(
        AskForApproval::Granular(GranularApprovalConfig {
            sandbox_approval: true,
            rules: true,
            skill_approval: true,
            request_permissions: false,
            mcp_elicitations: true,
        }),
        ApprovalsReviewer::User,
        &Policy::empty(),
        /*exec_permission_approvals_enabled*/ true,
        /*request_permissions_tool_enabled*/ true,
    );
    assert!(!rejected.contains("# request_permissions Tool"));
}

#[test]
fn granular_policy_lists_request_permissions_category_without_tool_section_when_tool_unavailable() {
    let text = approval_text(
        AskForApproval::Granular(GranularApprovalConfig {
            sandbox_approval: false,
            rules: false,
            skill_approval: false,
            request_permissions: true,
            mcp_elicitations: false,
        }),
        ApprovalsReviewer::User,
        &Policy::empty(),
        /*exec_permission_approvals_enabled*/ true,
        /*request_permissions_tool_enabled*/ false,
    );

    assert!(!text.contains("- `request_permissions`"));
    assert!(!text.contains("# request_permissions Tool"));
}
