#!/usr/bin/env bash
# shellcheck disable=SC2034,SC1090,SC1091,SC2155
# ============================================================================
# ClaudeSec Scanner — AI Security Best Practices Scanner
# ============================================================================
# Scans your environment for security best practices across:
#   - Infrastructure (Docker, Kubernetes, IaC)
#   - AI/LLM Security (model configs, prompt injection risks)
#   - Network (TLS, firewall, DNS)
#   - Cloud (AWS, GCP, Azure)
#   - Access Control (IAM, SSO, MFA)
#   - CI/CD (GitHub Actions, Jenkins, secrets)
#   - SaaS & Solutions (third-party integrations, API security)
#
# Usage:
#   claudesec scan [--category <cat>] [--format json|text] [--severity <level>]
#   claudesec scan --all
#   claudesec report [--output <file>]
#   claudesec init
#   claudesec version
# ============================================================================

set -uo pipefail

VERSION="0.7.2"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly CHECKS_DIR="$SCRIPT_DIR/checks"
readonly LIB_DIR="$SCRIPT_DIR/lib"

# Canonical scan-category order — the single source of truth. Both the full-scan
# and dashboard-scan paths expand this array, and scanner/lib/diagram-gen.py's
# CATEGORIES must mirror it (guarded by test_ci_diagram_gen_canonical_sync.py).
readonly -a CLAUDESEC_ALL_CATEGORIES=(
  infra ai network cloud access-control cicd code macos windows saas prowler
)

# Source library
source "$LIB_DIR/output.sh"
source "$LIB_DIR/checks.sh"
# shellcheck source=scanner/lib/datadog.sh
source "$LIB_DIR/datadog.sh"
# shellcheck source=scanner/lib/dashboard_serve.sh
source "$LIB_DIR/dashboard_serve.sh"

# Defaults
FORMAT="text"
SEVERITY="all"
CATEGORY="all"
OUTPUT_FILE=""
SCAN_DIR="${SCAN_DIR:-.}"
PARALLEL=0
BENCHMARK=0
declare -a ENV_SCAN_FILES=()

# Counters
TOTAL_CHECKS=0
PASSED=0
FAILED=0
WARNINGS=0
SKIPPED=0

# -- Config Loading -----------------------------------------------------------

load_config() {
  local config_file="$SCAN_DIR/.claudesec.yml"
  [[ -f "$config_file" ]] || return 0

  # Simple YAML parser for key settings (no external deps)
  local line key val
  while IFS= read -r line || [[ -n "$line" ]]; do
    # Strip comments and trim
    line="${line%%#*}"
    [[ -z "$line" || "$line" =~ ^[[:space:]]*$ ]] && continue

    # Match "key: value" patterns
    if [[ "$line" =~ ^[[:space:]]*severity:[[:space:]]*(.+) ]]; then
      SEVERITY="${BASH_REMATCH[1]// /}"
    fi
    if [[ "$line" =~ ^[[:space:]]*kubeconfig:[[:space:]]*(.+) ]]; then
      val="${BASH_REMATCH[1]// /}"
      val="${val/#\~/$HOME}"
      # Resolve relative to SCAN_DIR when path does not start with /
      if [[ -n "$val" && "$val" != /* ]] && [[ -n "${SCAN_DIR:-}" && -d "${SCAN_DIR:-}" ]]; then
        val="${SCAN_DIR}/${val}"
      fi
      [[ -z "${KUBECONFIG:-}" && -n "$val" ]] && export KUBECONFIG="$val"
    fi
    if [[ "$line" =~ ^[[:space:]]*kubecontext:[[:space:]]*(.+) ]]; then
      val="${BASH_REMATCH[1]// /}"
      [[ -z "${CLAUDESEC_KUBECONTEXT:-}" && -n "$val" ]] && export CLAUDESEC_KUBECONTEXT="$val"
    fi
    if [[ "$line" =~ ^[[:space:]]*aws_profile:[[:space:]]*(.+) ]]; then
      val="${BASH_REMATCH[1]// /}"
      [[ -z "${AWS_PROFILE:-}" && -n "$val" ]] && export AWS_PROFILE="$val" && export AWS_DEFAULT_PROFILE="$val"
    fi
    if [[ "$line" =~ ^[[:space:]]*aws_sso:[[:space:]]*(.+) ]]; then
      val="${BASH_REMATCH[1]}"
      val="${val// /}"
      val="${val//\"/}"
      val="${val//\'/}"
      if [[ "$val" == "all" ]]; then
        AWS_SSO_FORCE=1
        export AWS_SSO_SCAN_ALL=1
      elif [[ "$val" == "true" || "$val" == "yes" || "$val" == "1" ]]; then
        AWS_SSO_FORCE=1
      fi
    fi
    if [[ "$line" =~ ^[[:space:]]*aws_sso_login_timeout:[[:space:]]*([0-9]+) ]]; then
      val="${BASH_REMATCH[1]}"
      [[ -n "$val" ]] && export AWS_SSO_LOGIN_TIMEOUT="$val"
    fi
    if [[ "$line" =~ ^[[:space:]]*prowler_providers:[[:space:]]*(.+) ]]; then
      # Comma-separated provider list (e.g. aws,kubernetes,llm,googleworkspace,m365,cloudflare,nhn)
      val="${BASH_REMATCH[1]}"
      val="${val// /}"
      # Only set from file when not already set (env/CLI takes precedence)
      [[ -z "${CLAUDESEC_PROWLER_PROVIDERS:-}" && -n "$val" ]] && export CLAUDESEC_PROWLER_PROVIDERS="$val"
    fi
    if [[ "$line" =~ ^[[:space:]]*prowler_severity:[[:space:]]*(.+) ]]; then
      # Space-separated severities passed to Prowler (default: "medium high critical")
      val="${BASH_REMATCH[1]}"
      val="${val//\"/}"
      val="${val//\'/}"
      [[ -n "$val" ]] && export PROWLER_SEVERITY="$val"
    fi
    if [[ "$line" =~ ^[[:space:]]*prowler_timeout:[[:space:]]*([0-9]+) ]]; then
      val="${BASH_REMATCH[1]}"
      [[ -n "$val" ]] && export PROWLER_TIMEOUT="$val"
    fi
    if [[ "$line" =~ ^[[:space:]]*compliance:[[:space:]]*(.+) ]]; then
      val="${BASH_REMATCH[1]// /}"
      val="${val//\"/}"; val="${val//\'/}"
      [[ -z "${COMPLIANCE:-}" && -n "$val" ]] && COMPLIANCE="$val"
    fi
    if [[ "$line" =~ ^[[:space:]]*gcp_project:[[:space:]]*(.+) ]]; then
      val="${BASH_REMATCH[1]// /}"
      val="${val//\"/}"; val="${val//\'/}"
      [[ -z "${GCP_PROJECT:-}" && -n "$val" ]] && export GCP_PROJECT="$val"
    fi
    if [[ "$line" =~ ^[[:space:]]*google_workspace_customer_id:[[:space:]]*(.+) ]]; then
      val="${BASH_REMATCH[1]// /}"
      val="${val//\"/}"; val="${val//\'/}"
      [[ -z "${GOOGLE_WORKSPACE_CUSTOMER_ID:-}" && -n "$val" ]] && export GOOGLE_WORKSPACE_CUSTOMER_ID="$val"
    fi
    # Network & security tool scan (optional)
    if [[ "$line" =~ ^[[:space:]]*network_scan_targets:[[:space:]]*(.+) ]]; then
      val="${BASH_REMATCH[1]}"
      val="${val//\"/}"; val="${val//\'/}"; val="${val// /}"
      [[ -n "$val" ]] && export CLAUDESEC_NETWORK_SCAN_TARGETS="$val"
    fi
    if [[ "$line" =~ ^[[:space:]]*trivy_enabled:[[:space:]]*(.+) ]]; then
      val="${BASH_REMATCH[1]}"; val="${val//\"/}"; val="${val//\'/}"
      if [[ "$val" == "true" || "$val" == "yes" || "$val" == "1" ]]; then
        export CLAUDESEC_TRIVY_ENABLED=1
      elif [[ "$val" == "false" || "$val" == "no" || "$val" == "0" ]]; then
        export CLAUDESEC_TRIVY_ENABLED=0
      fi
    fi
    if [[ "$line" =~ ^[[:space:]]*network_scan_enabled:[[:space:]]*(.+) ]]; then
      val="${BASH_REMATCH[1]}"; val="${val//\"/}"; val="${val//\'/}"
      if [[ "$val" == "true" || "$val" == "yes" || "$val" == "1" ]]; then
        export CLAUDESEC_NETWORK_SCAN_ENABLED=1
      elif [[ "$val" == "false" || "$val" == "no" || "$val" == "0" ]]; then
        export CLAUDESEC_NETWORK_SCAN_ENABLED=0
      fi
    fi
  done < "$config_file"

  # Auto-discover kubeconfig from conventional paths when not set.
  # Reuses kubectl_auto_find_kubeconfig() from checks.sh (sourced above).
  if [[ -z "${KUBECONFIG:-}" ]]; then
    local auto_kube
    auto_kube=$(kubectl_auto_find_kubeconfig "${SCAN_DIR:-.}" 2>/dev/null) || true
    [[ -n "$auto_kube" && -f "$auto_kube" ]] && export KUBECONFIG="$auto_kube"
  fi
}

load_external_env() {
  # Default to a home-directory env file to avoid committing or referencing
  # personal filesystem layouts (e.g., Desktop paths).
  local env_file="${CLAUDESEC_ENV_FILE:-$HOME/.claudesec.env}"
  [[ -f "$env_file" ]] || return 0

  local line key val
  while IFS= read -r line || [[ -n "$line" ]]; do
    [[ "$line" =~ ^[[:space:]]*# ]] && continue
    [[ "$line" =~ ^[[:space:]]*$ ]] && continue
    if [[ "$line" =~ ^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*=[[:space:]]*(.*)[[:space:]]*$ ]]; then
      key="${BASH_REMATCH[1]}"
      val="${BASH_REMATCH[2]}"
      val="${val%%#*}"
      val="${val%\"}"; val="${val#\"}"
      val="${val%\'}"; val="${val#\'}"
      case "$key" in
        DD_API_KEY|DATADOG_API_KEY|DD_APP_KEY|DD_SITE|GOOGLE_WORKSPACE_CUSTOMER_ID|GOOGLE_APPLICATION_CREDENTIALS|GH_TOKEN|GITHUB_TOKEN|OKTA_OAUTH_TOKEN|OKTA_API_TOKEN|GITHUB_TOKEN_EXPIRES_AT|GH_TOKEN_EXPIRES_AT|OKTA_OAUTH_TOKEN_EXPIRES_AT|DATADOG_TOKEN_EXPIRES_AT|DD_TOKEN_EXPIRES_AT|DD_API_KEY_EXPIRES_AT|SLACK_TOKEN_EXPIRES_AT|SLACK_BOT_TOKEN_EXPIRES_AT|CLAUDESEC_STRICT_OKTA_SCOPES|CLAUDESEC_OKTA_REQUIRED_SCOPES|CLAUDESEC_TOKEN_EXPIRY_PROVIDERS|CLAUDESEC_TOKEN_EXPIRY_STRICT_PROVIDERS|CLAUDESEC_TOKEN_EXPIRY_WARNING_24H|CLAUDESEC_TOKEN_EXPIRY_WARNING_7D|ZSCALER_API_KEY|ZSCALER_API_ADMIN|ZSCALER_API_PASSWORD|ZSCALER_BASE_URL)
          if [[ -z "${!key:-}" && -n "$val" ]]; then
            export "$key=$val"
          fi
          ;;
        datadog_key_credential)
          if [[ -z "${DD_API_KEY:-}" && -z "${DATADOG_API_KEY:-}" && -n "$val" ]]; then
            export DD_API_KEY="$val"
          fi
          ;;
        datadog_app_key_credential)
          if [[ -z "${DD_APP_KEY:-}" && -n "$val" ]]; then
            export DD_APP_KEY="$val"
          fi
          ;;
      esac
    fi
  done < "$env_file"
}

validate_scan_dir() {
  # Canonicalize and validate SCAN_DIR to prevent path traversal
  if [[ -n "${SCAN_DIR:-}" && "$SCAN_DIR" != "." ]]; then
    if command -v realpath >/dev/null 2>&1; then
      SCAN_DIR="$(realpath -- "$SCAN_DIR" 2>/dev/null)" || true
    elif command -v readlink >/dev/null 2>&1; then
      SCAN_DIR="$(readlink -f -- "$SCAN_DIR" 2>/dev/null)" || true
    fi
  fi
  if [[ -n "${SCAN_DIR:-}" && "$SCAN_DIR" != "." && ! -d "$SCAN_DIR" ]]; then
    error "Invalid scan directory: $SCAN_DIR (does not exist or is not a directory)"
    exit 1
  fi
  export SCAN_DIR
}

# -- CLI Parsing --------------------------------------------------------------

usage() {
  cat <<'EOF'
ClaudeSec Scanner — AI Security Best Practices Scanner

Usage:
  claudesec scan [options]          Run security checks
  claudesec init                    Initialize .claudesec.yml config
  claudesec report [options]        Generate security report
  claudesec dashboard [file]         Generate HTML dashboard (default: claudesec-dashboard.html)
  claudesec diagrams [out_dir]       Generate draw.io architecture/flow/security-domains diagrams
  claudesec version                  Show version
  claudesec help                     Show this help

Scan Options:
  --category, -c <category>   Scan specific category:
                                 infra, ai, network, cloud, access-control,
                                 cicd, macos, windows, saas, prowler, all
  --severity, -s <level>      Minimum severity: critical, high, medium, low, all
  --format, -f <format>       Output format: text, json (default: text)
  --dir, -d <path>            Directory to scan (default: current directory)
  --output, -o <file>         Write results to file
  --compliance <framework>    Map to compliance framework:
                                 nist-csf, nist-800-53, iso27001,
                                 isms-p, soc2, pci-dss, kisa
  --quiet, -q                 Only show failures
  --aws-profile <profile>     Use specific AWS profile (triggers SSO login)
  --kubecontext <context>     Use specific kubectl context
  --kubeconfig <path>         Use specific kubeconfig file
  --kube-namespace <ns>       Scan specific Kubernetes namespace
  --aws-sso                   Force AWS SSO login before scan
  --env-file <path>           Scan external .env file for leaked secrets
  --parallel                   Run categories in parallel (faster full scans)
  --benchmark                  Run sequential + parallel and compare timing

Examples:
  claudesec scan                          # Full scan, current directory
  claudesec scan -c cloud -s high         # Cloud checks, high+ severity
  claudesec scan -c ai -f json            # AI checks, JSON output
  claudesec scan --compliance iso27001    # Scan with ISO 27001 mapping
  claudesec scan -c infra -c network      # Multiple categories
  claudesec scan --aws-profile prod       # Scan with AWS SSO profile
  claudesec scan --kubecontext staging    # Scan specific K8s context
  claudesec scan --kubeconfig ~/.kube/prod  # Use specific kubeconfig
  claudesec scan --kube-namespace default   # Scan specific namespace
  claudesec scan -c cloud --aws-sso       # Force SSO login for cloud scan
  claudesec dashboard --serve             # Full scan + generate + serve on http://127.0.0.1:11777/
  claudesec dashboard --all               # Full scan + generate dashboard (no server)
EOF
}

parse_args() {
  local cmd="${1:-help}"
  shift || true

  case "$cmd" in
    scan)
      local aws_sso_cli=0 aws_sso_all_cli=0
      while [[ $# -gt 0 ]]; do
        case "$1" in
          -c|--category)
            if [[ "$CATEGORY" == "all" ]]; then
              CATEGORY="$2"
            else
              CATEGORY="${CATEGORY},${2}"
            fi
            shift 2 ;;
          -s|--severity) SEVERITY="$2"; shift 2 ;;
          -f|--format) FORMAT="$2"; shift 2 ;;
          -d|--dir) SCAN_DIR="$2"; shift 2 ;;
          -o|--output) OUTPUT_FILE="$2"; shift 2 ;;
          --compliance) COMPLIANCE="$2"; shift 2 ;;
          -q|--quiet) QUIET=1; shift ;;
          --all) CATEGORY="all"; shift ;;
          --aws-profile) export AWS_PROFILE="$2"; export AWS_DEFAULT_PROFILE="$2"; shift 2 ;;
          --kubecontext) export CLAUDESEC_KUBECONTEXT="$2"; shift 2 ;;
          --kubeconfig) export KUBECONFIG="$2"; shift 2 ;;
          --kube-namespace) export CLAUDESEC_KUBE_NAMESPACE="$2"; shift 2 ;;
          --aws-sso) AWS_SSO_FORCE=1; aws_sso_cli=1; shift ;;
          --aws-sso-all) AWS_SSO_FORCE=1; export AWS_SSO_SCAN_ALL=1; aws_sso_all_cli=1; shift ;;
          --env-file) ENV_SCAN_FILES+=("$2"); shift 2 ;;
          --parallel) PARALLEL=1; shift ;;
          --benchmark) BENCHMARK=1; shift ;;
          -V|--version) echo "claudesec v$VERSION"; exit 0 ;;
          *) error "Unknown option: $1"; usage; exit 1 ;;
        esac
      done
      export CLAUDESEC_AWS_SSO_CLI="$aws_sso_cli"
      export CLAUDESEC_AWS_SSO_ALL_CLI="$aws_sso_all_cli"
      validate_scan_dir
      run_scan
      ;;
    init) run_init ;;
    report) run_report "$@" ;;
    dashboard)
      FORMAT="text"
      QUIET=1
      local html_output="claudesec-dashboard.html"
      local serve=0 serve_host="127.0.0.1" serve_port="11777"
      local dashboard_all=0
      local aws_sso_cli=0 aws_sso_all_cli=0
      while [[ $# -gt 0 ]]; do
        case "$1" in
          --aws-profile) export AWS_PROFILE="$2"; export AWS_DEFAULT_PROFILE="$2"; shift 2 ;;
          --aws-sso) AWS_SSO_FORCE=1; aws_sso_cli=1; shift ;;
          --aws-sso-all) AWS_SSO_FORCE=1; export AWS_SSO_SCAN_ALL=1; aws_sso_all_cli=1; shift ;;
          --kubecontext) export CLAUDESEC_KUBECONTEXT="$2"; shift 2 ;;
          --kubeconfig) export KUBECONFIG="$2"; shift 2 ;;
          --kube-namespace) export CLAUDESEC_KUBE_NAMESPACE="$2"; shift 2 ;;
          -c|--category) CATEGORY="$2"; shift 2 ;;
          -s|--severity) SEVERITY="$2"; shift 2 ;;
          -d|--dir) SCAN_DIR="$2"; shift 2 ;;
          --compliance) COMPLIANCE="$2"; shift 2 ;;
          --serve) serve=1; shift ;;
          --all) dashboard_all=1; shift ;;
          --host) serve_host="$2"; shift 2 ;;
          --port) serve_port="$2"; shift 2 ;;
          -*) shift ;;
          *) html_output="$1"; shift ;;
        esac
      done
      # Load .claudesec.yml so kubeconfig and aws_profile apply (e.g. for Prowler dashboard).
      load_config
      [[ -n "${COMPLIANCE:-}" ]] && export CLAUDESEC_COMPLIANCE="$COMPLIANCE"
      load_external_env
      if [[ ! -t 0 && "$aws_sso_cli" != "1" && "$aws_sso_all_cli" != "1" ]]; then
        unset AWS_SSO_SCAN_ALL AWS_SSO_FORCE 2>/dev/null || true
      fi
      # When dashboard includes prowler, allow AWS/K8s connection (SSO, kubeconfig).
      # Otherwise keep non-interactive for fast local-only dashboard.
      if [[ ",${CATEGORY}," != *",prowler,"* ]]; then
        export CLAUDESEC_NONINTERACTIVE=1
      fi
      # Avoid embedding identifiable cloud/user info into a shareable HTML file by default.
      export CLAUDESEC_DASHBOARD_SHOW_IDENTIFIERS="${CLAUDESEC_DASHBOARD_SHOW_IDENTIFIERS:-0}"
      # Default dashboard scope: local/static categories only (unless --serve or --all).
      # With --serve or --all we run full categories so the dashboard reflects entire scan.
      if [[ "${CATEGORY:-all}" == "all" && "$serve" != "1" && "$dashboard_all" != "1" ]]; then
        CATEGORY="access-control,cicd,code"
      fi
      # Tell the dashboard which categories actually EXECUTED. It otherwise
      # derives its category list from the findings, which cannot distinguish
      # "ran and was clean" from "never ran" — so the narrowing above produced a
      # headline grade from a fraction of the checks with nothing on screen
      # saying so (measured: Grade A / 97 over 40 checks, where `scan -c all`
      # on the same tree gives 90 over 180).
      export CLAUDESEC_SCAN_CATEGORIES="${CATEGORY:-all}"
      # For Prowler: if no AWS profile set, use first from ~/.aws/credentials
      if [[ ",${CATEGORY}," == *",prowler,"* && -z "${AWS_PROFILE:-}" ]]; then
        _first_profile=$(aws_default_or_first_profile 2>/dev/null || true)
        [[ -n "$_first_profile" ]] && export AWS_PROFILE="$_first_profile" && export AWS_DEFAULT_PROFILE="$_first_profile"
      fi
      # AWS credentials for dashboard when cloud/prowler included
      if [[ ",${CATEGORY}," == *",prowler,"* || ",${CATEGORY}," == *",cloud,"* ]]; then
        if [[ "${AWS_SSO_SCAN_ALL:-}" == "1" ]]; then
          info "Authenticating all AWS SSO profiles..."
          aws_sso_login_all_profiles || true
        elif [[ "${AWS_SSO_FORCE:-}" == "1" ]]; then
          info "Checking AWS credentials (--aws-sso)..."
          aws_sso_ensure_login || true
        elif ! has_aws_credentials 2>/dev/null; then
          if [[ -t 0 && "${CLAUDESEC_NONINTERACTIVE:-}" != "1" ]]; then
            info "AWS credentials required for cloud/prowler. Attempting SSO login..."
            aws_sso_ensure_login || true
          else
            info "AWS credentials required for cloud/prowler. Run in a terminal: aws sso login --profile <profile> or claudesec dashboard --aws-sso"
          fi
        fi
      fi
      run_scan_for_dashboard
      collect_datadog_dashboard_artifacts
      generate_html_dashboard "$html_output"
      save_scan_history 2>/dev/null || true
      echo -e "  ${GREEN}✓${NC} Dashboard saved to ${BOLD}$html_output${NC}"
      local history_count
      history_count=$(find "${SCAN_DIR}/.claudesec-history" -name 'scan-*.json' 2>/dev/null | wc -l | tr -d ' ')
      [[ "$history_count" -gt 0 ]] && echo -e "  ${DIM}Scan history: ${history_count} entries (${SCAN_DIR}/.claudesec-history/)${NC}"
      echo -e "  ${DIM}Open in browser: open $html_output${NC}"
      if [[ "$serve" == "1" ]]; then
        serve_dashboard "$html_output" "$serve_host" "$serve_port"
      fi
      ;;
    diagrams)
      SCAN_DIR="${SCAN_DIR:-$(pwd)}"
      export SCAN_DIR
      load_config
      local diagram_out="${1:-}"
      [[ -z "$diagram_out" ]] && diagram_out="${SCAN_DIR}/docs/architecture"
      if command -v python3 >/dev/null 2>&1 && [[ -f "$LIB_DIR/diagram-gen.py" ]]; then
        python3 "$LIB_DIR/diagram-gen.py" "$diagram_out"
        echo -e "  ${GREEN}✓${NC} Draw.io diagrams written to ${BOLD}$diagram_out${NC}"
      else
        error "python3 and diagram-gen.py required (not found under $LIB_DIR)"
        exit 1
      fi
      ;;
    version) echo "claudesec v$VERSION" ;;
    help|--help|-h) usage ;;
    *) error "Unknown command: $cmd"; usage; exit 1 ;;
  esac
}

# -- Core Functions -----------------------------------------------------------

run_scan() {
  local start_time
  start_time=$(date +%s)

  # Ensure SCAN_DIR exists and is a directory
  if [[ -z "${SCAN_DIR:-}" || ! -d "$SCAN_DIR" ]]; then
    SCAN_DIR="$(pwd)"
    export SCAN_DIR
  fi
  validate_scan_dir

  load_config
  [[ -n "${COMPLIANCE:-}" ]] && export CLAUDESEC_COMPLIANCE="$COMPLIANCE"
  load_external_env
  if [[ ! -t 0 && "${CLAUDESEC_AWS_SSO_CLI:-0}" != "1" && "${CLAUDESEC_AWS_SSO_ALL_CLI:-0}" != "1" ]]; then
    unset AWS_SSO_SCAN_ALL AWS_SSO_FORCE 2>/dev/null || true
  fi

  # Find profile/API key from config when not set (no slow network calls here)
  aws_ensure_profile_found 2>/dev/null || true
  gcp_ensure_credentials_found 2>/dev/null || true

  if [[ "$FORMAT" == "text" ]]; then
    print_banner
  fi

  # Determine which categories to scan (dedupe and filter)
  local categories=()
  if [[ "$CATEGORY" == "all" ]]; then
    categories=("${CLAUDESEC_ALL_CATEGORIES[@]}")
  else
    IFS=',' read -ra _raw <<< "$CATEGORY"
    for c in "${_raw[@]}"; do
      c="${c// /}"
      [[ -z "$c" ]] && continue
      # Avoid duplicates
      if [[ " ${categories[*]} " != *" $c "* ]]; then
        categories+=("$c")
      fi
    done
  fi

  # AWS credentials: force SSO if requested, or prompt when cloud/prowler and no creds
  local need_aws=0
  for c in "${categories[@]}"; do [[ "$c" == "cloud" || "$c" == "prowler" ]] && need_aws=1 && break; done
  if [[ "$need_aws" -eq 1 ]]; then
    if [[ "${AWS_SSO_SCAN_ALL:-}" == "1" ]]; then
      info "Authenticating all AWS SSO profiles..."
      aws_sso_login_all_profiles || true
    elif [[ "${AWS_SSO_FORCE:-}" == "1" ]]; then
      info "Checking AWS credentials (--aws-sso)..."
      aws_sso_ensure_login || true
    elif ! has_aws_credentials 2>/dev/null; then
      if [[ -t 0 && "${CLAUDESEC_NONINTERACTIVE:-}" != "1" ]]; then
        info "AWS credentials required for cloud/prowler. Attempting SSO login..."
        aws_sso_ensure_login || true
      else
        info "AWS credentials required for cloud/prowler. Run in a terminal: aws sso login --profile <profile> or claudesec scan --aws-sso (or --aws-profile <name>)"
      fi
    fi
  fi

  local categories_csv
  categories_csv=$(IFS=,; echo "${categories[*]}")
  configure_prowler_provider_defaults "$categories_csv"

  # Reset per-scan state so each run is independent
  unset _KUBECTL_ENSURE_ACCESS_DONE 2>/dev/null || true

  # Run checks for each category
  run_category_checks "$PARALLEL" 1 "${categories[@]}"

  # Print summary
  local end_time
  end_time=$(date +%s)
  local duration=$((end_time - start_time))

  # `-o` used to parse its argument, print "Report saved to <file>", and write
  # NOTHING — a documented flag that reported success for work it never did.
  # The redirection below is on a function call, not a pipeline, so the report
  # renderer still runs in THIS shell and its global counters stay intact.
  _emit_report() {
    if [[ "$FORMAT" == "text" ]]; then
      print_summary "$duration"
    elif [[ "$FORMAT" == "json" ]]; then
      print_json_summary "$duration"
    fi
  }

  if [[ -n "$OUTPUT_FILE" ]]; then
    if _emit_report > "$OUTPUT_FILE"; then
      # stdout still gets the report, which is what `-o` did before (minus the
      # file); dropping that would break anyone piping the output today.
      cat "$OUTPUT_FILE"
      success "Report saved to $OUTPUT_FILE"
    else
      error "Could not write report to $OUTPUT_FILE"
      _emit_report
    fi
  else
    _emit_report
  fi

  # Benchmark mode: run sequential + parallel and compare
  if [[ "$BENCHMARK" -eq 1 && ${#categories[@]} -gt 1 ]]; then
    run_benchmark "${categories[@]}"
  fi

  # Exit code based on failures
  if [[ $FAILED -gt 0 ]]; then
    exit 1
  fi
}

run_benchmark() {
  local bench_categories=("$@")
  local bench_tmpdir
  bench_tmpdir=$(mktemp -d)

  echo ""
  echo -e "${BOLD}${CYAN}╔══════════════════════════════════════════════════════════╗${NC}"
  echo -e "${BOLD}${CYAN}║              SCAN PERFORMANCE BENCHMARK                  ║${NC}"
  echo -e "${BOLD}${CYAN}╚══════════════════════════════════════════════════════════╝${NC}"
  echo ""

  # ── Sequential per-category timing ──
  echo -e "  ${BOLD}Sequential (per-category):${NC}"
  local seq_total=0
  for bcat in "${bench_categories[@]}"; do
    local bcheck_dir="$CHECKS_DIR/$bcat"
    [[ -d "$bcheck_dir" ]] || continue
    local bstart bend bdur
    bstart=$(date +%s%N 2>/dev/null || date +%s)
    (
      set +o pipefail
      TOTAL_CHECKS=0; PASSED=0; FAILED=0; WARNINGS=0; SKIPPED=0
      JSON_RESULTS="[]"
      FINDINGS_CRITICAL=(); FINDINGS_HIGH=(); FINDINGS_MEDIUM=(); FINDINGS_LOW=()
      for check_file in "$bcheck_dir"/*.sh; do
        [[ -f "$check_file" ]] || continue
        source "$check_file" >/dev/null 2>&1 || true
      done
    ) > /dev/null 2>&1
    bend=$(date +%s%N 2>/dev/null || date +%s)
    if [[ ${#bstart} -gt 10 ]]; then
      bdur=$(( (bend - bstart) / 1000000 ))
      printf "    %-20s %5d ms\n" "$bcat" "$bdur"
      seq_total=$((seq_total + bdur))
    else
      bdur=$((bend - bstart))
      printf "    %-20s %5d s\n" "$bcat" "$bdur"
      seq_total=$((seq_total + bdur * 1000))
    fi
  done

  # ── Parallel timing ──
  local par_start par_end par_dur
  par_start=$(date +%s%N 2>/dev/null || date +%s)
  local bpids=()
  for bcat in "${bench_categories[@]}"; do
    local bcheck_dir="$CHECKS_DIR/$bcat"
    [[ -d "$bcheck_dir" ]] || continue
    (
      set +o pipefail
      TOTAL_CHECKS=0; PASSED=0; FAILED=0; WARNINGS=0; SKIPPED=0
      JSON_RESULTS="[]"
      FINDINGS_CRITICAL=(); FINDINGS_HIGH=(); FINDINGS_MEDIUM=(); FINDINGS_LOW=()
      for check_file in "$bcheck_dir"/*.sh; do
        [[ -f "$check_file" ]] || continue
        source "$check_file" >/dev/null 2>&1 || true
      done
    ) > /dev/null 2>&1 &
    bpids+=($!)
  done
  for bpid in "${bpids[@]}"; do wait "$bpid" 2>/dev/null || true; done
  par_end=$(date +%s%N 2>/dev/null || date +%s)

  echo ""
  if [[ ${#par_start} -gt 10 ]]; then
    par_dur=$(( (par_end - par_start) / 1000000 ))
    printf "  ${BOLD}Sequential total:${NC}  %5d ms\n" "$seq_total"
    printf "  ${BOLD}Parallel total:${NC}    %5d ms\n" "$par_dur"
    if [[ $par_dur -gt 0 ]]; then
      local speedup=$(( (seq_total * 100) / par_dur ))
      printf "  ${BOLD}Speedup:${NC}           %d.%02dx\n" "$((speedup / 100))" "$((speedup % 100))"
    fi
  else
    par_dur=$((par_end - par_start))
    printf "  ${BOLD}Sequential total:${NC}  %5d ms\n" "$seq_total"
    printf "  ${BOLD}Parallel total:${NC}    %5d s\n" "$par_dur"
  fi

  echo ""
  echo -e "  ${DIM}Benchmark ran ${#bench_categories[@]} categories · $(date '+%Y-%m-%d %H:%M:%S')${NC}"
  echo ""
  rm -r "$bench_tmpdir" 2>/dev/null || true
}

run_init() {
  local config_file="$SCAN_DIR/.claudesec.yml"
  if [[ -f "$config_file" ]]; then
    warning "$config_file already exists"
    return
  fi

  cat > "$config_file" <<'YAML'
# ClaudeSec Scanner Configuration
# See: https://github.com/your-username/claudesec

version: "0.1"

scan:
  # Categories to scan (default: all)
  categories:
    - infra
    - ai
    - network
    - cloud
    - access-control
    - cicd
    - saas

  # Minimum severity to report: critical, high, medium, low
  severity: low

  # Compliance framework mapping (optional)
  # compliance: iso27001

  # Paths to exclude from scanning
  exclude:
    - node_modules/
    - .git/
    - vendor/
    - dist/

# Prowler / live scan (optional — CLI --kubeconfig/--aws-profile override these)
# kubeconfig: ~/.kube/config
# aws_profile: default
# prowler_providers: aws,kubernetes,llm,googleworkspace,m365,cloudflare,nhn
# prowler_severity: "medium high critical"
# prowler_timeout: 600

# Cloud provider settings
cloud:
  aws:
    enabled: true
    # profile: default
    # regions: [us-east-1, ap-northeast-2]
  gcp:
    enabled: false
    # project: my-project
  azure:
    enabled: false
    # subscription: my-subscription

# Custom check overrides
overrides:
  # Disable specific checks
  # disabled:
  #   - INFRA-001
  #   - NET-003
YAML

  success "Created $config_file"
  info "Edit this file to customize your scan settings."
}

run_report() {
  info "Generating security report..."
  run_scan
}

configure_prowler_provider_defaults() {
  local categories_csv="$1"
  [[ ",${categories_csv}," == *",prowler,"* ]] || return 0

  if [[ -z "${CLAUDESEC_PROWLER_PROVIDERS:-}" ]]; then
    export CLAUDESEC_PROWLER_PROVIDERS="aws,gcp,googleworkspace,kubernetes"
    if [[ "$FORMAT" == "text" && -z "${QUIET:-}" ]]; then
      info "Prowler providers: defaulting to aws,gcp,googleworkspace,kubernetes (override with prowler_providers in .claudesec.yml)"
    fi
  fi
}

run_scan_for_dashboard() {
  local start_time
  start_time=$(date +%s)

  # Collect environment info for dashboard
  collect_environment_info 2>/dev/null || true

  local categories=()
  if [[ "$CATEGORY" == "all" ]]; then
    categories=("${CLAUDESEC_ALL_CATEGORIES[@]}")
  else
    IFS=',' read -ra categories <<< "$CATEGORY"
  fi

  local categories_csv
  categories_csv=$(IFS=,; echo "${categories[*]}")
  configure_prowler_provider_defaults "$categories_csv"

  run_category_checks "${CLAUDESEC_DASHBOARD_PARALLEL:-0}" 0 "${categories[@]}"

  SCAN_DURATION=$(( $(date +%s) - start_time ))
}

# -- Entry Point --------------------------------------------------------------

parse_args "$@"
