# frozen_string_literal: true

require "test_helper"

class Api::V1::BaseControllerTest < ActionDispatch::IntegrationTest
  setup do
    @user = users(:family_admin)
    @oauth_app = Doorkeeper::Application.create!(
      name: "Test API App",
      redirect_uri: "https://example.com/callback",
      scopes: "read read_write"
    )

    # Clean up any existing API keys for the test user
    @user.api_keys.destroy_all

    # Create a test API key
    @plain_api_key = "base_test_#{SecureRandom.hex(8)}"
    @api_key = ApiKey.create!(
      user: @user,
      name: "Test API Key",
      display_key: @plain_api_key,
      scopes: [ "read_write" ]
    )

    # Clear any existing rate limit data
    Redis.new.del("api_rate_limit:#{@api_key.id}")
  end

  teardown do
    # Clean up Redis data after each test
    Redis.new.del("api_rate_limit:#{@api_key.id}")
  end

  test "should require authentication" do
    # Test that endpoints require OAuth tokens
    get "/api/v1/test"
    assert_response :unauthorized

    response_body = JSON.parse(response.body)
    assert_equal "unauthorized", response_body["error"]
  end

  test "should authenticate with valid access token" do
    # Create a valid access token
    access_token = Doorkeeper::AccessToken.create!(
      application: @oauth_app,
      resource_owner_id: @user.id,
      scopes: "read"
    )

    get "/api/v1/test", params: {}, headers: {
      "Authorization" => "Bearer #{access_token.token}"
    }

    # Should not be unauthorized when token is valid
    assert_response :success
    response_body = JSON.parse(response.body)
    assert_equal "test_success", response_body["message"]
    assert_equal @user.email, response_body["user"]
  end

  test "should reject revoked access token" do
    access_token = Doorkeeper::AccessToken.create!(
      application: @oauth_app,
      resource_owner_id: @user.id,
      scopes: "read"
    )
    access_token.revoke

    get "/api/v1/test", params: {}, headers: {
      "Authorization" => "Bearer #{access_token.token}"
    }

    assert_response :unauthorized
    response_body = JSON.parse(response.body)
    assert_equal "unauthorized", response_body["error"]
  end

  test "should reject invalid access token" do
    get "/api/v1/test", params: {}, headers: {
      "Authorization" => "Bearer invalid_token"
    }

    assert_response :unauthorized
    response_body = JSON.parse(response.body)
    assert_equal "unauthorized", response_body["error"]
  end

  test "should authenticate with valid API key" do
    get "/api/v1/test", params: {}, headers: {
      "X-Api-Key" => @plain_api_key
    }

    assert_response :success
    response_body = JSON.parse(response.body)
    assert_equal "test_success", response_body["message"]
    assert_equal @user.email, response_body["user"]
  end

  test "should reject invalid API key" do
    get "/api/v1/test", params: {}, headers: {
      "X-Api-Key" => "invalid_api_key"
    }

    assert_response :unauthorized
    response_body = JSON.parse(response.body)
    assert_equal "unauthorized", response_body["error"]
    assert_includes response_body["message"], "Access token or API key"
  end

  test "should reject expired API key" do
    @api_key.update!(expires_at: 1.day.ago)

    get "/api/v1/test", params: {}, headers: {
      "X-Api-Key" => @plain_api_key
    }

    assert_response :unauthorized
    response_body = JSON.parse(response.body)
    assert_equal "unauthorized", response_body["error"]
  end

  test "should reject revoked API key" do
    @api_key.revoke!

    get "/api/v1/test", params: {}, headers: {
      "X-Api-Key" => @plain_api_key
    }

    assert_response :unauthorized
    response_body = JSON.parse(response.body)
    assert_equal "unauthorized", response_body["error"]
  end

  test "should update last_used_at when API key is used" do
    original_time = @api_key.last_used_at

    get "/api/v1/test", params: {}, headers: {
      "X-Api-Key" => @plain_api_key
    }

    assert_response :success
    @api_key.reload
    assert_not_equal original_time, @api_key.last_used_at
    assert @api_key.last_used_at > (original_time || Time.at(0))
  end

  test "should prioritize OAuth over API key when both are provided" do
    access_token = Doorkeeper::AccessToken.create!(
      application: @oauth_app,
      resource_owner_id: @user.id,
      scopes: "read"
    )

    # Capture log output to verify OAuth is used
    logs = capture_log do
      get "/api/v1/test", params: {}, headers: {
        "Authorization" => "Bearer #{access_token.token}",
        "X-Api-Key" => @plain_api_key
      }
    end

    assert_response :success
    assert_includes logs, "OAuth Token"
    assert_not_includes logs, "API Key:"
  end

  test "should provide current_scopes for API key authentication" do
    get "/api/v1/test_scope_required", params: {}, headers: {
      "X-Api-Key" => @plain_api_key
    }

    assert_response :success
    response_body = JSON.parse(response.body)
    assert_equal "scope_authorized", response_body["message"]
    assert_includes response_body["scopes"], "read_write"
  end

  test "should authorize API key with required scope" do
    get "/api/v1/test_scope_required", params: {}, headers: {
      "X-Api-Key" => @plain_api_key
    }

    assert_response :success
    response_body = JSON.parse(response.body)
    assert_equal "scope_authorized", response_body["message"]
    assert_equal "write", response_body["required_scope"]
  end

  test "should reject API key without required scope" do
    # Revoke existing API key and create one with limited scopes
    @api_key.revoke!
    limited_api_key = ApiKey.create!(
      user: @user,
      name: "Limited API Key",
      display_key: "limited_key_#{SecureRandom.hex(8)}",
      scopes: [ "read" ]  # Only read scope
    )

    get "/api/v1/test_scope_required", params: {}, headers: {
      "X-Api-Key" => limited_api_key.display_key
    }

    assert_response :forbidden
    response_body = JSON.parse(response.body)
    assert_equal "insufficient_scope", response_body["error"]
    assert_includes response_body["message"], "write"
  end

  test "should authorize API key with multiple required scopes" do
    get "/api/v1/test_multiple_scopes_required", params: {}, headers: {
      "X-Api-Key" => @plain_api_key
    }

    assert_response :success
    response_body = JSON.parse(response.body)
    assert_equal "read_scope_authorized", response_body["message"]
    assert_includes response_body["scopes"], "read_write"
  end

  test "should reject API key missing one of multiple required scopes" do
    # The multiple scopes test now just checks for "read" permission,
    # so we need to create an API key without any scopes at all.
    # First revoke the existing key, then create one with empty scopes array won't work due to validation.
    # Instead, we'll test by trying to access the write endpoint with a read-only key.
    @api_key.revoke!

    read_only_key = ApiKey.create!(
      user: @user,
      name: "Read Only API Key",
      display_key: "read_only_key_#{SecureRandom.hex(8)}",
      scopes: [ "read" ]  # Only read scope, no write
    )

    # Try to access the write-requiring endpoint with read-only key
    get "/api/v1/test_scope_required", params: {}, headers: {
      "X-Api-Key" => read_only_key.display_key
    }

    assert_response :forbidden
    response_body = JSON.parse(response.body)
    assert_equal "insufficient_scope", response_body["error"]
  end

  test "should log API access with API key information" do
    logs = capture_log do
      get "/api/v1/test", params: {}, headers: {
        "X-Api-Key" => @plain_api_key
      }
    end

    assert_includes logs, "API Request"
    assert_includes logs, "GET /api/v1/test"
    assert_includes logs, @user.email
  end

  test "should provide current_resource_owner method" do
    # This will be tested through the test controller once implemented
    skip "Will test via test controller implementation"
  end

  test "should handle ActiveRecord::RecordNotFound errors" do
    access_token = Doorkeeper::AccessToken.create!(
      application: @oauth_app,
      resource_owner_id: @user.id,
      scopes: "read"
    )

    # This will trigger a not found error in the test controller
    get "/api/v1/test_not_found", params: {}, headers: {
      "Authorization" => "Bearer #{access_token.token}"
    }

    assert_response :not_found
    response_body = JSON.parse(response.body)
    assert_equal "record_not_found", response_body["error"]
  end

  test "should log API access" do
    access_token = Doorkeeper::AccessToken.create!(
      application: @oauth_app,
      resource_owner_id: @user.id,
      scopes: "read"
    )

    # Capture log output
    logs = capture_log do
      get "/api/v1/test", params: {}, headers: {
        "Authorization" => "Bearer #{access_token.token}"
      }
    end

    assert_includes logs, "API Request"
    assert_includes logs, "GET /api/v1/test"
    assert_includes logs, @user.email
  end

  test "should enforce family-based access control" do
    # Create another family user
    other_family = families(:dylan_family)
    other_user = users(:family_member)
    other_user.update!(family: other_family)

    access_token = Doorkeeper::AccessToken.create!(
      application: @oauth_app,
      resource_owner_id: other_user.id,
      scopes: "read"
    )

    # Try to access data from a different family
    get "/api/v1/test_family_access", params: {}, headers: {
      "Authorization" => "Bearer #{access_token.token}"
    }

    assert_response :forbidden
    response_body = JSON.parse(response.body)
    assert_equal "forbidden", response_body["error"]
  end

  test "should enforce family-based access control with API key" do
    # Create API key for a user in a different family
    other_family = families(:dylan_family)
    other_user = users(:family_member)
    other_user.update!(family: other_family)
    other_user.api_keys.destroy_all

    other_user_api_key = ApiKey.create!(
      user: other_user,
      name: "Other User API Key",
      display_key: "other_user_key_#{SecureRandom.hex(8)}",
      scopes: [ "read" ]
    )

    # Try to access data from a different family
    get "/api/v1/test_family_access", params: {}, headers: {
      "X-Api-Key" => other_user_api_key.display_key
    }

    assert_response :forbidden
    response_body = JSON.parse(response.body)
    assert_equal "forbidden", response_body["error"]
  end

  test "should include rate limit headers on successful API key requests" do
    get "/api/v1/test", headers: { "X-Api-Key" => @plain_api_key }

    assert_response :success
    assert_not_nil response.headers["X-RateLimit-Limit"]
    assert_not_nil response.headers["X-RateLimit-Remaining"]
    assert_not_nil response.headers["X-RateLimit-Reset"]

    assert_equal "100", response.headers["X-RateLimit-Limit"]
    assert_equal "99", response.headers["X-RateLimit-Remaining"]
  end

  test "should increment rate limit count with each request" do
    # First request
    get "/api/v1/test", headers: { "X-Api-Key" => @plain_api_key }
    assert_response :success
    assert_equal "99", response.headers["X-RateLimit-Remaining"]

    # Second request
    get "/api/v1/test", headers: { "X-Api-Key" => @plain_api_key }
    assert_response :success
    assert_equal "98", response.headers["X-RateLimit-Remaining"]
  end

  test "should return 429 when rate limit exceeded" do
    # Make 100 requests to exhaust the rate limit
    100.times do
      get "/api/v1/test", headers: { "X-Api-Key" => @plain_api_key }
      assert_response :success
    end

    # 101st request should be rate limited
    get "/api/v1/test", headers: { "X-Api-Key" => @plain_api_key }
    assert_response :too_many_requests

    response_body = JSON.parse(response.body)
    assert_equal "rate_limit_exceeded", response_body["error"]
    assert_includes response_body["message"], "Rate limit exceeded"

    # Check response headers
    assert_equal "100", response.headers["X-RateLimit-Limit"]
    assert_equal "0", response.headers["X-RateLimit-Remaining"]
    assert_not_nil response.headers["X-RateLimit-Reset"]
    assert_not_nil response.headers["Retry-After"]
  end

  test "should not apply rate limiting to OAuth requests" do
    # This would need to be implemented based on your OAuth setup
    # For now, just verify that requests without API keys don't trigger rate limiting
    get "/api/v1/test"
    assert_response :unauthorized

    # Should not have rate limit headers for unauthorized requests
    assert_nil response.headers["X-RateLimit-Limit"]
  end

  test "should provide detailed rate limit information in 429 response" do
    # Exhaust the rate limit
    100.times do
      get "/api/v1/test", headers: { "X-Api-Key" => @plain_api_key }
    end

    # Make the rate-limited request
    get "/api/v1/test", headers: { "X-Api-Key" => @plain_api_key }
    assert_response :too_many_requests

    response_body = JSON.parse(response.body)
    assert_equal "rate_limit_exceeded", response_body["error"]
    assert response_body["details"]["limit"] == 100
    assert response_body["details"]["current"] >= 100
    assert response_body["details"]["reset_in_seconds"] > 0
  end

  test "rate limiting should be per API key" do
    # Create a second user for independent API keys
    other_user = users(:family_member)
    other_api_key = ApiKey.create!(
      user: other_user,
      name: "Other Test API Key",
      scopes: [ "read" ],
      display_key: "other_rate_test_#{SecureRandom.hex(8)}"
    )

    begin
      # Make 50 requests with first API key
      50.times do
        get "/api/v1/test", headers: { "X-Api-Key" => @plain_api_key }
        assert_response :success
      end

      # Should still be able to make requests with second API key
      get "/api/v1/test", headers: { "X-Api-Key" => other_api_key.display_key }
      assert_response :success
      assert_equal "99", response.headers["X-RateLimit-Remaining"]
    ensure
      Redis.new.del("api_rate_limit:#{other_api_key.id}")
      other_api_key.destroy
    end
  end

private

  def capture_log(&block)
    io = StringIO.new
    original_logger = Rails.logger
    Rails.logger = Logger.new(io)

    yield

    io.string
  ensure
    Rails.logger = original_logger
  end
end
