defmodule Raxol.Security.AuditorTest do
  use ExUnit.Case, async: true

  alias Raxol.Security.Auditor

  describe "validate_input/3" do
    test "accepts valid text input" do
      assert {:ok, "Hello World"} = Auditor.validate_input("Hello World", :text)
    end

    test "detects SQL injection patterns" do
      malicious_inputs = [
        "'; DROP TABLE users; --",
        "1' OR '1'='1",
        "admin'--",
        "1' UNION SELECT * FROM passwords"
      ]

      for input <- malicious_inputs do
        assert {:error, :critical, _} = Auditor.validate_input(input, :sql)
      end
    end

    test "detects XSS patterns" do
      xss_inputs = [
        "<script>alert('XSS')</script>",
        "<img src=x onerror=alert('XSS')>",
        "javascript:alert('XSS')",
        "<iframe src='evil.com'></iframe>"
      ]

      for input <- xss_inputs do
        assert {:error, :high, _} = Auditor.validate_input(input, :html)
      end
    end

    test "detects path traversal patterns" do
      traversal_inputs = [
        "../../../etc/passwd",
        "..\\..\\windows\\system32",
        "%2e%2e%2f%2e%2e%2f",
        "....//....//etc/passwd"
      ]

      for input <- traversal_inputs do
        assert {:error, :high, _} = Auditor.validate_input(input, :path)
      end
    end

    test "sanitizes HTML input" do
      input = "<p>Hello <script>alert('XSS')</script>World</p>"
      {:ok, sanitized} = Auditor.validate_input(input, :text, sanitize: true)

      refute sanitized =~ "<script>"
      assert sanitized =~ "Hello"
      assert sanitized =~ "World"
    end

    test "enforces length limits" do
      long_input = String.duplicate("a", 1001)

      assert {:error, :low, _} =
               Auditor.validate_input(long_input, :text, max_length: 1000)
    end

    test "validates encoding" do
      invalid_utf8 = <<0xFF, 0xFE>>
      assert {:error, :high, _} = Auditor.validate_input(invalid_utf8, :text)
    end
  end

  describe "validate_credentials/2" do
    test "accepts valid credentials" do
      # Mock setup for rate limiting
      :ets.new(:rate_limits, [:set, :public, :named_table])

      assert {:ok, :valid} =
               Auditor.validate_credentials("john_doe", "StrongP@ssw0rd")
    end

    test "rejects short usernames" do
      assert {:error, :low, _} =
               Auditor.validate_credentials("ab", "StrongP@ssw0rd")
    end

    test "rejects invalid username characters" do
      assert {:error, :medium, _} =
               Auditor.validate_credentials("user@name", "StrongP@ssw0rd")
    end

    test "enforces password complexity" do
      weak_passwords = [
        # Too short
        "short",
        # No uppercase
        "alllowercase",
        # No lowercase  
        "ALLUPPERCASE",
        # No digits
        "NoNumbers",
        # No letters
        "12345678"
      ]

      for password <- weak_passwords do
        assert {:error, _, _} =
                 Auditor.validate_credentials("validuser", password)
      end
    end
  end

  describe "authorize_action/3" do
    test "allows authorized actions" do
      user = %{id: 1, permissions: %{read: true}}

      assert {:ok, :authorized} =
               Auditor.authorize_action(user, "resource", :read)
    end

    test "denies unauthorized actions" do
      user = %{id: 1, permissions: %{read: true}}

      assert {:error, :high, _} =
               Auditor.authorize_action(user, "resource", :write)
    end
  end

  describe "validate_sql_query/2" do
    test "accepts safe queries" do
      safe_queries = [
        "SELECT * FROM users WHERE id = ?",
        "INSERT INTO logs (message) VALUES (?)",
        "UPDATE users SET name = ? WHERE id = ?"
      ]

      for query <- safe_queries do
        assert {:ok, {^query, []}} = Auditor.validate_sql_query(query, [])
      end
    end

    test "rejects queries with injection patterns" do
      unsafe_queries = [
        "SELECT * FROM users WHERE name = 'admin'--'",
        "DROP TABLE users; --",
        "SELECT * FROM users UNION SELECT * FROM passwords"
      ]

      for query <- unsafe_queries do
        assert {:error, :critical, _} = Auditor.validate_sql_query(query)
      end
    end

    test "validates query parameters" do
      query = "SELECT * FROM users WHERE id = ?"

      assert {:ok, _} = Auditor.validate_sql_query(query, [123])
      assert {:ok, _} = Auditor.validate_sql_query(query, ["safe_string"])

      assert {:error, :high, _} =
               Auditor.validate_sql_query(query, ["'; DROP TABLE users; --"])
    end
  end

  describe "check_rate_limit/3" do
    setup do
      :ets.new(:rate_limits, [:set, :public, :named_table])
      :ok
    end

    test "allows requests within limit" do
      identifier = "user123"

      for _ <- 1..10 do
        assert {:ok, :allowed} =
                 Auditor.check_rate_limit(identifier, :api_call, limit: 10)
      end
    end

    test "blocks requests exceeding limit" do
      identifier = "user456"

      # Make requests up to limit
      for _ <- 1..5 do
        assert {:ok, :allowed} =
                 Auditor.check_rate_limit(identifier, :login, limit: 5)
      end

      # Next request should be blocked
      assert {:error, :medium, _} =
               Auditor.check_rate_limit(identifier, :login, limit: 5)
    end
  end

  describe "validate_csrf_token/2" do
    test "accepts matching tokens" do
      token = "valid_token_123"
      assert {:ok, :valid} = Auditor.validate_csrf_token(token, token)
    end

    test "rejects mismatched tokens" do
      assert {:error, :high, _} =
               Auditor.validate_csrf_token("token1", "token2")
    end

    test "rejects nil tokens" do
      assert {:error, :high, _} = Auditor.validate_csrf_token("token", nil)
      assert {:error, :high, _} = Auditor.validate_csrf_token(nil, "token")
    end
  end

  describe "validate_security_headers/1" do
    test "accepts secure headers" do
      headers = %{
        "content-security-policy" => "default-src 'self'",
        "x-content-type-options" => "nosniff",
        "x-frame-options" => "DENY",
        "strict-transport-security" => "max-age=31536000"
      }

      assert {:ok, :secure} = Auditor.validate_security_headers(headers)
    end

    test "detects missing security headers" do
      headers = %{
        "content-type" => "text/html"
      }

      assert {:error, :medium, message} =
               Auditor.validate_security_headers(headers)

      assert message =~ "Security headers issues"
    end

    test "detects invalid header values" do
      headers = %{
        "content-security-policy" => "unsafe-inline",
        "x-content-type-options" => "wrong",
        "x-frame-options" => "INVALID",
        "strict-transport-security" => "no-max-age"
      }

      assert {:error, :medium, _} = Auditor.validate_security_headers(headers)
    end
  end

  describe "sanitize_html/1" do
    test "removes script tags" do
      html = "<p>Hello <script>alert('XSS')</script>World</p>"
      sanitized = Auditor.sanitize_html(html)

      refute sanitized =~ "<script>"
      refute sanitized =~ "alert"
    end

    test "removes event handlers" do
      html = ~s|<img src="pic.jpg" onerror="alert('XSS')">|
      sanitized = Auditor.sanitize_html(html)

      refute sanitized =~ "onerror"
      refute sanitized =~ "alert"
    end

    test "encodes special characters" do
      html = ~s(<p>5 < 10 & 10 > 5</p>)
      sanitized = Auditor.sanitize_html(html)

      assert sanitized =~ "&lt;"
      assert sanitized =~ "&gt;"
      assert sanitized =~ "&amp;"
    end

    test "preserves safe content" do
      html = ~s(<p>This is <strong>safe</strong> content</p>)
      sanitized = Auditor.sanitize_html(html)

      assert sanitized =~ "This is"
      assert sanitized =~ "safe"
      assert sanitized =~ "content"
    end
  end

  describe "validate_file_upload/2" do
    test "placeholder for file upload tests" do
      # TODO: Implement actual file upload tests
      assert true
    end
  end
end
