# frozen_string_literal: true

require "test_helper"

class EventTest < ActiveSupport::TestCase
  def setup
    @sample_payload = {
      "request_id" => "test-event-123",
      "timestamp" => Time.now.iso8601,
      "request" => {
        "ip" => "192.168.1.1",
        "method" => "GET",
        "path" => "/api/test",
        "headers" => {
          "host" => "example.com",
          "user-agent" => "TestAgent/1.0",
          "content-type" => "application/json"
        },
        "query" => { "param" => "value" }
      },
      "response" => {
        "status_code" => 200,
        "duration_ms" => 150,
        "size" => 1024
      },
      "waf_action" => "allow",
      "server_name" => "test-server",
      "environment" => "test",
      "geo" => {
        "country_code" => "US",
        "city" => "Test City"
      },
      "tags" => { "source" => "test" },
      "agent" => {
        "name" => "baffle-agent",
        "version" => "1.0.0"
      }
    }
  end

  def teardown
    Event.delete_all  # Delete events first to avoid foreign key constraints
  end

  test "create_from_waf_payload! creates event with proper enum values" do
    event = Event.create_from_waf_payload!("test-123", @sample_payload)

    assert event.persisted?
    assert_equal "test-123", event.request_id
    assert_equal "192.168.1.1", event.ip_address
    assert_equal "/api/test", event.request_path
    assert_equal 200, event.response_status
    assert_equal 150, event.response_time_ms
    assert_equal "test-server", event.server_name
    assert_equal "test", event.environment
    assert_equal "US", event.country_code
    assert_equal "Test City", event.city
    assert_equal "baffle-agent", event.agent_name
    assert_equal "1.0.0", event.agent_version
  end

  test "create_from_waf_payload! properly normalizes request_method enum" do
    test_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]
    expected_enums = [:get, :post, :put, :patch, :delete, :head, :options]

    test_methods.each_with_index do |method, index|
      payload = @sample_payload.dup
      payload["request"]["method"] = method
      payload["request_id"] = "test-method-#{method.downcase}"

      event = Event.create_from_waf_payload!("test-method-#{method.downcase}", payload)

      assert_equal expected_enums[index].to_s, event.request_method,
                   "Method #{method} should map to enum #{expected_enums[index]}"
      assert_equal index, event.request_method_before_type_cast,
                   "Method #{method} should be stored as integer #{index}"
    end
  end

  test "create_from_waf_payload! properly normalizes waf_action enum" do
    # Updated enum values: deny:0, allow:1, redirect:2, challenge:3, log:4
    test_actions = [
      ["deny", :deny, 0],
      ["block", :deny, 0],
      ["allow", :allow, 1],
      ["pass", :allow, 1],
      ["redirect", :redirect, 2],
      ["challenge", :challenge, 3],
      ["log", :log, 4],
      ["monitor", :log, 4],
      ["unknown", :allow, 1]  # Default fallback
    ]

    test_actions.each do |action, expected_enum, expected_int|
      payload = @sample_payload.dup
      payload["waf_action"] = action
      payload["request_id"] = "test-action-#{action}"

      event = Event.create_from_waf_payload!("test-action-#{action}", payload)

      assert_equal expected_enum.to_s, event.waf_action,
                   "Action #{action} should map to enum #{expected_enum}"
      assert_equal expected_int, event.waf_action_before_type_cast,
                   "Action #{action} should be stored as integer #{expected_int}"
    end
  end

  test "create_from_waf_payload! handles header case normalization" do
    payload = @sample_payload.dup
    payload["request"]["headers"] = {
      "HOST" => "EXAMPLE.COM",
      "User-Agent" => "TestAgent/1.0",
      "CONTENT-TYPE" => "application/json"
    }

    event = Event.create_from_waf_payload!("test-headers", payload)

    assert_equal "TestAgent/1.0", event.user_agent
    # The normalize_payload_headers method should normalize header keys to lowercase
    # but keep values as-is
    assert_equal "EXAMPLE.COM", event.headers["host"]
    assert_equal "application/json", event.headers["content-type"]
  end

  test "enum values persist after save and reload" do
    event = Event.create_from_waf_payload!("test-persist", @sample_payload)

    # Verify initial values (updated enum: deny:0, allow:1)
    assert_equal "get", event.request_method
    assert_equal "allow", event.waf_action
    assert_equal 0, event.request_method_before_type_cast
    assert_equal 1, event.waf_action_before_type_cast  # allow is now 1

    # Reload from database
    event.reload

    # Values should still be correct (allow is now 1)
    assert_equal "get", event.request_method
    assert_equal "allow", event.waf_action
    assert_equal 0, event.request_method_before_type_cast
    assert_equal 1, event.waf_action_before_type_cast
  end

  test "enum scopes work correctly" do
    # Create events with different methods and actions using completely separate payloads

    # Event 1: GET + allow
    Event.create_from_waf_payload!("get-allow", {
      "request_id" => "get-allow",
      "timestamp" => Time.now.iso8601,
      "request" => {
        "ip" => "192.168.1.1",
        "method" => "GET",
        "path" => "/test",
        "headers" => { "host" => "example.com" }
      },
      "response" => { "status_code" => 200 },
      "waf_action" => "allow"
    }, @project)

    # Event 2: POST + allow
    Event.create_from_waf_payload!("post-allow", {
      "request_id" => "post-allow",
      "timestamp" => Time.now.iso8601,
      "request" => {
        "ip" => "192.168.1.1",
        "method" => "POST",
        "path" => "/test",
        "headers" => { "host" => "example.com" }
      },
      "response" => { "status_code" => 200 },
      "waf_action" => "allow"
    }, @project)

    # Event 3: GET + deny
    Event.create_from_waf_payload!("get-deny", {
      "request_id" => "get-deny",
      "timestamp" => Time.now.iso8601,
      "request" => {
        "ip" => "192.168.1.1",
        "method" => "GET",
        "path" => "/test",
        "headers" => { "host" => "example.com" }
      },
      "response" => { "status_code" => 200 },
      "waf_action" => "deny"
    }, @project)

    # Test method scopes - use string values for enum queries
    get_events = Event.where(request_method: "get")
    post_events = Event.where(request_method: "post")

    assert_equal 2, get_events.count
    assert_equal 1, post_events.count

    # Test action scopes - use string values for enum queries
    allowed_events = Event.where(waf_action: "allow")
    denied_events = Event.where(waf_action: "deny")

    assert_equal 2, allowed_events.count
    assert_equal 1, denied_events.count
  end

  test "event normalization is triggered when needed" do
    # Create event without enum values (simulating old data)
    event = Event.create!(
      project: @project,
      request_id: "normalization-test",
      timestamp: Time.current,
      payload: @sample_payload,
      ip_address: "192.168.1.1",
      request_path: "/test",
      # Don't set request_method or waf_action to trigger normalization
      request_method: nil,
      waf_action: nil
    )

    # Manually set the raw values that would normally be extracted
    event.instance_variable_set(:@raw_request_method, "POST")
    event.instance_variable_set(:@raw_action, "deny")

    # Trigger normalization
    event.send(:normalize_event_fields)
    event.save!

    # Verify normalization worked
    event.reload
    assert_equal "post", event.request_method
    assert_equal "deny", event.waf_action
    assert_equal 1, event.request_method_before_type_cast  # POST = 1
    assert_equal 1, event.waf_action_before_type_cast      # DENY = 1
  end

  test "payload extraction methods work correctly" do
    event = Event.create_from_waf_payload!("extraction-test", @sample_payload)

    # Test request_details
    request_details = event.request_details
    assert_equal "192.168.1.1", request_details[:ip]
    assert_equal "GET", request_details[:method]
    assert_equal "/api/test", request_details[:path]
    assert_equal "example.com", request_details[:headers]["host"]

    # Test response_details
    response_details = event.response_details
    assert_equal 200, response_details[:status_code]
    assert_equal 150, response_details[:duration_ms]
    assert_equal 1024, response_details[:size]

    # Test geo_details
    geo_details = event.geo_details
    assert_equal "US", geo_details["country_code"]
    assert_equal "Test City", geo_details["city"]

    # Test tags
    tags = event.tags
    assert_equal "test", tags["source"]
  end

  test "helper methods work correctly" do
    event = Event.create_from_waf_payload!("helper-test", @sample_payload)

    # Test boolean methods
    assert event.allowed?
    assert_not event.blocked?
    assert_not event.logged?  # Changed from rate_limited? to logged?
    assert_not event.challenged?
    assert_not event.rule_matched?

    # Test path methods
    assert_equal ["api", "test"], event.path_segments
    assert_equal 2, event.path_depth
  end

  test "timestamp parsing works with various formats" do
    timestamps = [
      Time.now.iso8601,
      (Time.now.to_f * 1000).to_i,  # Unix timestamp in milliseconds
      Time.now.utc                   # Time object
    ]

    timestamps.each_with_index do |timestamp, index|
      payload = @sample_payload.dup
      payload["timestamp"] = timestamp
      payload["request_id"] = "timestamp-test-#{index}"

      event = Event.create_from_waf_payload!("timestamp-test-#{index}", payload)
      assert event.timestamp.is_a?(Time), "Timestamp #{index} should be parsed as Time"
      assert_not event.timestamp.nil?
    end
  end

  test "handles missing optional fields gracefully" do
    minimal_payload = {
      "request_id" => "minimal-test",
      "timestamp" => Time.now.iso8601,
      "request" => {
        "ip" => "10.0.0.1",
        "method" => "GET",
        "path" => "/simple"
      },
      "response" => {
        "status_code" => 404
      }
    }

    event = Event.create_from_waf_payload!("minimal-test", minimal_payload)

    assert event.persisted?
    assert_equal "10.0.0.1", event.ip_address
    assert_equal "get", event.request_method
    assert_equal "/simple", event.request_path
    assert_equal 404, event.response_status

    # Optional fields should be nil
    assert_nil event.user_agent
    assert_nil event.response_time_ms
    assert_nil event.country_code
    assert_nil event.city
    assert_nil event.agent_name
    assert_nil event.agent_version
  end
end