local helpers = require "spec.helpers"
local cjson = require "cjson"

local REDIS_HOST = helpers.redis_host
local REDIS_PORT = helpers.redis_port
local REDIS_PASSWORD = ""
local REDIS_DATABASE = 3

-- This performs the test up to two times (and no more than two).
-- We are **not** retrying to "give it another shot" in case of a flaky test.
-- The reason why we allow for a single retry in this test suite is because
-- tests are dependent on the value of the current minute. If the minute
-- flips during the test (i.e. going from 03:43:59 to 03:44:00), the result
-- will fail. Since each test takes less than a minute to run, running it
-- a second time right after that failure ensures that another flip will
-- not occur. If the second execution failed as well, this means that there
-- was an actual problem detected by the test.
local function it_with_retry(desc, test)
    return it(desc, function(...)
        if not pcall(test, ...) then
            ngx.sleep(60 - (ngx.now() % 60)) -- Wait for minute to expire
            test(...)
        end
    end)
end

local function flush_redis()
    local redis = require "resty.redis"
    local red = redis:new()
    red:set_timeout(2000)
    local ok, err = red:connect(REDIS_HOST, REDIS_PORT)
    if not ok then
        error("failed to connect to Redis: " .. err)
    end

    if REDIS_PASSWORD and REDIS_PASSWORD ~= "" then
        local ok, err = red:auth(REDIS_PASSWORD)
        if not ok then
            error("failed to connect to Redis: " .. err)
        end
    end

    local ok, err = red:select(REDIS_DATABASE)
    if not ok then
        error("failed to change Redis database: " .. err)
    end

    red:flushall()
    red:close()
end

for _, strategy in helpers.each_strategy() do
    describe("Plugin: rate-limit error message [#" .. strategy .. "]", function()
        local proxy_client
        lazy_setup(function()
            flush_redis()

            local bp = helpers.get_db_utils(strategy)

            -- Test service for "inner" error format with second limit
            local service1 = bp.services:insert{
                protocol = "http",
                host = helpers.mock_upstream_host,
                port = helpers.mock_upstream_port
            }

            local route1 = bp.routes:insert{
                protocols = {"http", "https"},
                hosts = {"test-inner-second.com"},
                service = service1
            }

            bp.plugins:insert{
                route = {id = route1.id},
                name = "rate-limit",
                config = {
                    second = 1,
                    fault_tolerant = false,
                    redis_host = REDIS_HOST,
                    redis_port = REDIS_PORT,
                    redis_database = REDIS_DATABASE,
                    error_format = "inner",
                    use_dynamic_error_message = true
                }
            }

            -- Test service for "inner" error format with hour limit
            local service2 = bp.services:insert{
                protocol = "http",
                host = helpers.mock_upstream_host,
                port = helpers.mock_upstream_port
            }

            local route2 = bp.routes:insert{
                protocols = {"http", "https"},
                hosts = {"test-inner-hour.com"},
                service = service2
            }

            bp.plugins:insert{
                route = {id = route2.id},
                name = "rate-limit",
                config = {
                    hour = 10,
                    fault_tolerant = false,
                    redis_host = REDIS_HOST,
                    redis_port = REDIS_PORT,
                    redis_database = REDIS_DATABASE,
                    error_format = "inner",
                    use_dynamic_error_message = true
                }
            }

            -- Test service for "openapi" error format with minute limit
            local service3 = bp.services:insert{
                protocol = "http",
                host = helpers.mock_upstream_host,
                port = helpers.mock_upstream_port
            }

            local route3 = bp.routes:insert{
                protocols = {"http", "https"},
                hosts = {"test-openapi-minute.com"},
                service = service3
            }

            bp.plugins:insert{
                route = {id = route3.id},
                name = "rate-limit",
                config = {
                    minute = 5,
                    fault_tolerant = false,
                    redis_host = REDIS_HOST,
                    redis_port = REDIS_PORT,
                    redis_database = REDIS_DATABASE,
                    error_format = "openapi",
                    use_dynamic_error_message = true
                }
            }

            -- Test service for backward compatibility (without use_dynamic_error_message)
            local service4 = bp.services:insert{
                protocol = "http",
                host = helpers.mock_upstream_host,
                port = helpers.mock_upstream_port
            }

            local route4 = bp.routes:insert{
                protocols = {"http", "https"},
                hosts = {"test-legacy.com"},
                service = service4
            }

            bp.plugins:insert{
                route = {id = route4.id},
                name = "rate-limit",
                config = {
                    minute = 3,
                    fault_tolerant = false,
                    redis_host = REDIS_HOST,
                    redis_port = REDIS_PORT,
                    redis_database = REDIS_DATABASE,
                    error_format = "openapi"
                    -- use_dynamic_error_message not set, should default to false
                }
            }

            assert(helpers.start_kong({
                database = strategy,
                nginx_conf = "spec/fixtures/custom_nginx.template",
                plugins = "rate-limit"
            }))
        end)

        lazy_teardown(function()
            helpers.stop_kong()
        end)

        before_each(function()
            proxy_client = helpers.proxy_client()
        end)

        after_each(function()
            if proxy_client then
                proxy_client:close()
            end
        end)

        describe("Rate Limit Error Message", function()
            it_with_retry("error message shows correct limit for second (inner format)", function()
                -- First request should succeed
                local res = assert(proxy_client:send({
                    method = "GET",
                    path = "/status/200",
                    headers = {["Host"] = "test-inner-second.com"}
                }))
                assert.res_status(200, res)
                helpers.wait_timer("rate-limiting", true, "any-finish")

                -- Second request should be rate limited
                local res = assert(proxy_client:send({
                    method = "GET",
                    path = "/status/200",
                    headers = {["Host"] = "test-inner-second.com"}
                }))
                assert.res_status(429, res)
                
                local body = res:read_body()
                local json = cjson.decode(body)
                
                -- Verify the message contains the correct limit and period
                assert.is_not_nil(json.message)
                assert.matches("1 request per second", json.message)
                assert.not_matches("API key", json.message)
                assert.not_matches("1000 requests per minute", json.message)
            end)

            it_with_retry("error message shows correct limit for hour (inner format)", function()
                -- Send 10 requests (should succeed)
                for i = 1, 10 do
                    local res = assert(proxy_client:send({
                        method = "GET",
                        path = "/status/200",
                        headers = {["Host"] = "test-inner-hour.com"}
                    }))
                    assert.res_status(200, res)
                    helpers.wait_timer("rate-limiting", true, "any-finish")
                end

                -- 11th request should be rate limited
                local res = assert(proxy_client:send({
                    method = "GET",
                    path = "/status/200",
                    headers = {["Host"] = "test-inner-hour.com"}
                }))
                assert.res_status(429, res)
                
                local body = res:read_body()
                local json = cjson.decode(body)
                
                -- Verify the message contains the correct limit and period
                assert.is_not_nil(json.message)
                assert.matches("10 requests per hour", json.message)
                assert.not_matches("API key", json.message)
                assert.not_matches("1000 requests per minute", json.message)
            end)

            it_with_retry("error message shows correct limit for minute (openapi format)", function()
                -- Send 5 requests (should succeed)
                for i = 1, 5 do
                    local res = assert(proxy_client:send({
                        method = "GET",
                        path = "/status/200",
                        headers = {["Host"] = "test-openapi-minute.com"}
                    }))
                    assert.res_status(200, res)
                    helpers.wait_timer("rate-limiting", true, "any-finish")
                end

                -- 6th request should be rate limited
                local res = assert(proxy_client:send({
                    method = "GET",
                    path = "/status/200",
                    headers = {["Host"] = "test-openapi-minute.com"}
                }))
                assert.res_status(429, res)
                
                local body = res:read_body()
                local json = cjson.decode(body)
                
                -- Verify the message contains the correct limit and period
                assert.is_not_nil(json.message)
                assert.matches("5 requests per minute", json.message)
                assert.not_matches("API key", json.message)
                assert.not_matches("1000 requests per minute", json.message)
            end)

            it_with_retry("legacy behavior preserved when use_dynamic_error_message is false", function()
                -- Send 3 requests (should succeed)
                for i = 1, 3 do
                    local res = assert(proxy_client:send({
                        method = "GET",
                        path = "/status/200",
                        headers = {["Host"] = "test-legacy.com"}
                    }))
                    assert.res_status(200, res)
                    helpers.wait_timer("rate-limiting", true, "any-finish")
                end

                -- 4th request should be rate limited with OLD hardcoded message
                local res = assert(proxy_client:send({
                    method = "GET",
                    path = "/status/200",
                    headers = {["Host"] = "test-legacy.com"}
                }))
                assert.res_status(429, res)
                
                local body = res:read_body()
                local json = cjson.decode(body)
                
                -- Verify the message contains the OLD hardcoded message
                assert.is_not_nil(json.message)
                assert.matches("API key rate limit exceeded", json.message)
                assert.matches("1000 requests per minute", json.message)
                -- Should NOT have dynamic message
                assert.not_matches("3 requests per minute", json.message)
            end)
        end)
    end)
end
