///|
/// Secure headers middleware — adds common security headers and CSRF protection.
///
/// Security headers middleware, similar to helmet, with CSRF token support.
///
/// ## Usage
///
/// ```
/// // Security headers only
/// app.use(secure(SecureConfig::default()))
///
/// // With CSRF protection
/// app.use(secure(SecureConfig::default().with_csrf()))
/// ```

///|
/// Secure headers configuration.
pub(all) struct SecureConfig {
  /// X-Frame-Options: DENY | SAMEORIGIN | ALLOW-FROM uri
  /// Prevents clickjacking. Default: "DENY"
  frame_options : String
  /// X-Content-Type-Options: nosniff
  /// Prevents MIME type sniffing. Default: true
  content_type_nosniff : Bool
  /// X-XSS-Protection: 1; mode=block
  /// Enables browser XSS filter. Default: true
  xss_protection : Bool
  /// Content-Security-Policy header value.
  /// Example: "default-src 'self'"
  /// Empty = not set. Default: ""
  content_security_policy : String
  /// Referrer-Policy header value.
  /// Example: "strict-origin-when-cross-origin"
  /// Empty = not set. Default: "strict-origin-when-cross-origin"
  referrer_policy : String
  /// Strict-Transport-Security (HSTS) header value.
  /// Example: "max-age=31536000; includeSubDomains"
  /// Empty = not set. Default: ""
  strict_transport_security : String
  /// Permissions-Policy header (formerly Feature-Policy).
  /// Example: "camera=(), microphone=()"
  /// Empty = not set. Default: ""
  permissions_policy : String
  /// Cross-Origin-Opener-Policy header.
  /// Example: "same-origin"
  /// Empty = not set. Default: "same-origin"
  cross_origin_opener_policy : String
  /// Whether to set headers for API responses (skip HTML-only headers).
  /// When true, skips X-Frame-Options (not relevant for APIs).
  /// Default: false
  is_api : Bool
  /// Whether to skip HSTS header (useful for local development).
  /// Default: false
  skip_hsts : Bool
  /// === CSRF Protection ===
  /// Enable CSRF token validation
  enable_csrf : Bool
  /// CSRF token secret (used to sign tokens). Auto-generated if empty.
  csrf_secret : String
  /// CSRF cookie name. Default: "_csrf"
  csrf_cookie_name : String
  /// CSRF header name to check. Default: "X-CSRF-Token"
  csrf_header_name : String
  /// CSRF form field name to check. Default: "_csrf"
  csrf_form_name : String
  /// HTTP methods that are safe (no CSRF check). Default: GET, HEAD, OPTIONS, TRACE
  safe_methods : Array[String]
  /// Paths to exclude from CSRF protection
  csrf_exclude_paths : Array[String]
  /// CSRF token expiry in seconds. Default: 86400 (24h)
  csrf_token_expiry : Int
}

///|
/// Default secure configuration — balanced security for web applications.

pub fn SecureConfig::default() -> SecureConfig {
  {
    frame_options: "DENY",
    content_type_nosniff: true,
    xss_protection: true,
    content_security_policy: "",
    referrer_policy: "strict-origin-when-cross-origin",
    strict_transport_security: "",
    permissions_policy: "",
    cross_origin_opener_policy: "same-origin",
    is_api: false,
    skip_hsts: false,
    enable_csrf: false,
    csrf_secret: "",
    csrf_cookie_name: "_csrf",
    csrf_header_name: "X-CSRF-Token",
    csrf_form_name: "_csrf",
    safe_methods: ["GET", "HEAD", "OPTIONS", "TRACE"],
    csrf_exclude_paths: [],
    csrf_token_expiry: 86400,
  }
}

///|
/// Default secure configuration for APIs (no frame options, no CSP).
pub fn SecureConfig::api_default() -> SecureConfig {
  {
    frame_options: "",
    content_type_nosniff: true,
    xss_protection: true,
    content_security_policy: "",
    referrer_policy: "strict-origin-when-cross-origin",
    strict_transport_security: "",
    permissions_policy: "",
    cross_origin_opener_policy: "same-origin",
    is_api: true,
    skip_hsts: false,
    enable_csrf: false,
    csrf_secret: "",
    csrf_cookie_name: "_csrf",
    csrf_header_name: "X-CSRF-Token",
    csrf_form_name: "_csrf",
    safe_methods: ["GET", "HEAD", "OPTIONS", "TRACE"],
    csrf_exclude_paths: [],
    csrf_token_expiry: 86400,
  }
}

///|
/// Enable CSRF protection on this config.
pub fn SecureConfig::with_csrf(self : SecureConfig) -> SecureConfig {
  {
    frame_options: self.frame_options,
    content_type_nosniff: self.content_type_nosniff,
    xss_protection: self.xss_protection,
    content_security_policy: self.content_security_policy,
    referrer_policy: self.referrer_policy,
    strict_transport_security: self.strict_transport_security,
    permissions_policy: self.permissions_policy,
    cross_origin_opener_policy: self.cross_origin_opener_policy,
    is_api: self.is_api,
    skip_hsts: self.skip_hsts,
    enable_csrf: true,
    csrf_secret: self.csrf_secret,
    csrf_cookie_name: self.csrf_cookie_name,
    csrf_header_name: self.csrf_header_name,
    csrf_form_name: self.csrf_form_name,
    safe_methods: self.safe_methods,
    csrf_exclude_paths: self.csrf_exclude_paths,
    csrf_token_expiry: self.csrf_token_expiry,
  }
}

///|
/// Set a custom CSRF secret.
pub fn SecureConfig::with_csrf_secret(self : SecureConfig, secret : String) -> SecureConfig {
  {
    frame_options: self.frame_options,
    content_type_nosniff: self.content_type_nosniff,
    xss_protection: self.xss_protection,
    content_security_policy: self.content_security_policy,
    referrer_policy: self.referrer_policy,
    strict_transport_security: self.strict_transport_security,
    permissions_policy: self.permissions_policy,
    cross_origin_opener_policy: self.cross_origin_opener_policy,
    is_api: self.is_api,
    skip_hsts: self.skip_hsts,
    enable_csrf: self.enable_csrf,
    csrf_secret: secret,
    csrf_cookie_name: self.csrf_cookie_name,
    csrf_header_name: self.csrf_header_name,
    csrf_form_name: self.csrf_form_name,
    safe_methods: self.safe_methods,
    csrf_exclude_paths: self.csrf_exclude_paths,
    csrf_token_expiry: self.csrf_token_expiry,
  }
}

///|
/// Add paths to exclude from CSRF protection.
pub fn SecureConfig::with_csrf_exclude(
  self : SecureConfig,
  paths : Array[String],
) -> SecureConfig {
  {
    frame_options: self.frame_options,
    content_type_nosniff: self.content_type_nosniff,
    xss_protection: self.xss_protection,
    content_security_policy: self.content_security_policy,
    referrer_policy: self.referrer_policy,
    strict_transport_security: self.strict_transport_security,
    permissions_policy: self.permissions_policy,
    cross_origin_opener_policy: self.cross_origin_opener_policy,
    is_api: self.is_api,
    skip_hsts: self.skip_hsts,
    enable_csrf: self.enable_csrf,
    csrf_secret: self.csrf_secret,
    csrf_cookie_name: self.csrf_cookie_name,
    csrf_header_name: self.csrf_header_name,
    csrf_form_name: self.csrf_form_name,
    safe_methods: self.safe_methods,
    csrf_exclude_paths: paths,
    csrf_token_expiry: self.csrf_token_expiry,
  }
}

///|
/// Generate a CSRF token based on session and secret.
fn generate_csrf_token(secret : String, now : UInt64) -> String {
  // Simplified CSRF token: timestamp + hash(secret + timestamp)
  // In production, use @encoding.base64 + @hash.sha256
  let timestamp = now.to_string()
  let raw = secret + ":" + timestamp
  let hash_val = simple_token_hash(raw)
  timestamp + ":" + hash_val
}

///|
/// Simple token hash for CSRF.
fn simple_token_hash(s : String) -> String {
  let mut h : Int64 = 5381L
  let chars = s.to_array()
  for i = 0; i < chars.length(); i = i + 1 {
    let byte_val = chars[i].to_int().to_int64()
    h = ((h << 5) + h) + byte_val
  }
  h.to_string()
}

///|
/// Validate a CSRF token.
fn validate_csrf_token(token : String, secret : String, max_age : Int) -> Bool {
  match token.find(":") {
    Some(pos) => {
      let timestamp_str = token[:pos].to_owned()
      let hash_val = token[pos + 1:].to_owned()
      let expected = simple_token_hash(secret + ":" + timestamp_str)
      if hash_val != expected {
        return false
      }
      // Check expiry
      let token_time = @string.parse_int64(timestamp_str) catch { _ => return false }
      let now = @env.now().reinterpret_as_int64()
      if now - token_time > max_age.to_int64() {
        return false
      }
      true
    }
    None => false
  }
}

///|
/// Check if a path should be excluded from CSRF protection.
fn is_csrf_excluded(path : String, exclude_paths : Array[String]) -> Bool {
  for p in exclude_paths {
    if path == p || path.has_prefix(p) {
      return true
    }
  }
  false
}

///|
/// Secure headers middleware with optional CSRF protection.
///
/// ```
/// // Web app
/// app.use(secure(SecureConfig::default()))
///
/// // With CSRF protection
/// app.use(secure(SecureConfig::default().with_csrf()))
///
/// // API server
/// app.use(secure(SecureConfig::api_default()))
/// ```
pub fn secure(config : SecureConfig) -> Handler {
  // Generate a default secret if none provided
  let secret = if config.csrf_secret == "" {
    @env.now().to_string() + "_mbit_csrf"
  } else {
    config.csrf_secret
  }

  async fn(ctx) {
    // === Security Headers ===
    // X-Frame-Options (skip for APIs)
    if config.frame_options != "" {
      ctx.set_header("X-Frame-Options", config.frame_options)
    }

    // X-Content-Type-Options
    if config.content_type_nosniff {
      ctx.set_header("X-Content-Type-Options", "nosniff")
    }

    // X-XSS-Protection
    if config.xss_protection {
      ctx.set_header("X-XSS-Protection", "1; mode=block")
    }

    // Content-Security-Policy
    if config.content_security_policy != "" {
      ctx.set_header("Content-Security-Policy", config.content_security_policy)
    }

    // Referrer-Policy
    if config.referrer_policy != "" {
      ctx.set_header("Referrer-Policy", config.referrer_policy)
    }

    // Strict-Transport-Security (HSTS)
    if config.strict_transport_security != "" && !config.skip_hsts {
      ctx.set_header(
        "Strict-Transport-Security",
        config.strict_transport_security,
      )
    }

    // Permissions-Policy
    if config.permissions_policy != "" {
      ctx.set_header("Permissions-Policy", config.permissions_policy)
    }

    // Cross-Origin-Opener-Policy
    if config.cross_origin_opener_policy != "" {
      ctx.set_header(
        "Cross-Origin-Opener-Policy",
        config.cross_origin_opener_policy,
      )
    }

    // === CSRF Protection ===
    if config.enable_csrf {
      let path = ctx.path()

      // Skip excluded paths
      if !is_csrf_excluded(path, config.csrf_exclude_paths) {
        let _method = ctx.http_method()

        // Check if this is a safe method
        let mut is_safe = false
        for safe in config.safe_methods {
          if safe == _method {
            is_safe = true
            break
          }
        }

        if is_safe {
          // For safe methods, generate and set CSRF cookie
          let token = generate_csrf_token(secret, @env.now())
          ctx.set_cookie(
            config.csrf_cookie_name,
            token,
            http_only=true,
            same_site="Strict",
            secure=true,
            path="/",
          )
          // Store token in context for template rendering
          ctx.set("csrf_token", Json::string(token))
        } else {
          // For unsafe methods, validate CSRF token
          // Check header first, then form field
          let csrf_token = match ctx.header(config.csrf_header_name) {
            Some(t) => Some(t)
            None => {
              // Try form field
              let form = ctx.body_string()
              let field_prefix = config.csrf_form_name + "="
              match form.find(field_prefix) {
                Some(start) => {
                  let after = start + field_prefix.length()
                  match form[after:].find("&") {
                    Some(end) => Some(form[after:after + end].to_owned())
                    None => Some(form[after:].to_owned())
                  }
                }
                None => None
              }
            }
          }

          match csrf_token {
            Some(token) => {
              if !validate_csrf_token(token, secret, config.csrf_token_expiry) {
                ctx.abort_with_status(403, "CSRF token invalid or expired")
                return
              }
            }
            None => {
              ctx.abort_with_status(403, "CSRF token missing")
              return
            }
          }
        }
      }
    }

    ctx.next()
  }
}