--- Unit tests for irsa.lua
-- Tests IRSA class methods and integration

-- Add the parent directory to the Lua path
local script_path = debug.getinfo(1, "S").source:sub(2)
local plugin_dir = script_path:match("(.*/)")
plugin_dir = plugin_dir:gsub("spec/$", "")

-- Add to package.path
package.path = plugin_dir .. "?.lua;" .. plugin_dir .. "?/init.lua;" .. package.path

-- Mock dependencies before loading the module
_G.kong = {
  log = {
    notice = function() end,
    err = function() end,
  }
}

_G.ngx = {
  escape_uri = function(str)
    return str:gsub("[^%w%-%.%_%~]", function(c)
      return string.format("%%%02X", string.byte(c))
    end)
  end
}

-- Mock resty.lock
package.loaded["resty.lock"] = {
  new = function()
    return {
      lock = function() return true end,
      unlock = function() return true end,
    }
  end
}

-- Mock resty.lrucache
package.loaded["resty.lrucache"] = {
  new = function()
    local cache = {}
    return {
      get = function(self, key) return cache[key] end,
      set = function(self, key, value) cache[key] = value end,
      delete = function(self, key) cache[key] = nil end,
      flush_all = function(self) cache = {} end,
    }
  end
}

-- Mock resty.http
local mock_http_response = {
  status = 200,
  body = [[
    <AssumeRoleWithWebIdentityResponse>
      <AssumeRoleWithWebIdentityResult>
        <Credentials>
          <AccessKeyId>ASIAMOCKACCESSKEY</AccessKeyId>
          <SecretAccessKey>MockSecretKey123</SecretAccessKey>
          <SessionToken>MockSessionToken123</SessionToken>
          <Expiration>2024-12-31T23:59:59Z</Expiration>
        </Credentials>
      </AssumeRoleWithWebIdentityResult>
    </AssumeRoleWithWebIdentityResponse>
  ]]
}

package.loaded["resty.http"] = {
  new = function()
    return {
      set_timeout = function() end,
      request_uri = function(self, url, opts)
        return mock_http_response, nil
      end
    }
  end
}

package.loaded["kong.plugins.o11y-proxy.http_client"] = {
  new = function()
    return {
      set_timeout = function() end,
      request_uri = function(self, url, opts)
        return mock_http_response, nil
      end
    }
  end
}

package.loaded["cjson.safe"] = {}

package.loaded["kong.plugins.o11y-proxy.utils"] = {
  parse_iso8601 = function(date_str)
    if date_str and date_str ~= "" then
      -- Return a future timestamp for testing
      return os.time() + 3600
    end
    return nil
  end
}

-- Save original os functions before mocking
local original_os_time = os.time
local original_os_date = os.date
local original_os_remove = os.remove

-- Mock environment variables to return nil
_G.os = {
  getenv = function(name)
    return nil
  end,
  time = original_os_time,
  date = original_os_date,
  remove = original_os_remove,
}

local irsa = dofile(plugin_dir .. "irsa.lua")

describe("irsa.lua", function()
  local test_token_file = "/tmp/irsa_test_token_" .. os.time()
  
  before_each(function()
    -- Create test token file
    local file = io.open(test_token_file, "w")
    if file then
      file:write("test-web-identity-token")
      file:close()
    end
  end)
  
  after_each(function()
    -- Clean up
    os.remove(test_token_file)
  end)
  
  describe("IRSA.new", function()
    it("should create a new IRSA instance with required parameters", function()
      local irsa_obj = irsa.new({
        token_file = test_token_file,
        role_arn = "arn:aws:iam::123456789012:role/TestRole",
      })
      
      assert.is_not_nil(irsa_obj)
      assert.equals(test_token_file, irsa_obj.token_file)
      assert.equals("arn:aws:iam::123456789012:role/TestRole", irsa_obj.role_arn)
      assert.equals("kong-plugin-session", irsa_obj.session_name)
      assert.equals("us-east-1", irsa_obj.region)
    end)
    
    it("should use custom session_name when provided", function()
      local irsa_obj = irsa.new({
        token_file = test_token_file,
        role_arn = "arn:aws:iam::123456789012:role/TestRole",
        session_name = "custom-session-name",
      })
      
      assert.equals("custom-session-name", irsa_obj.session_name)
    end)
    
    it("should use custom region when provided", function()
      local irsa_obj = irsa.new({
        token_file = test_token_file,
        role_arn = "arn:aws:iam::123456789012:role/TestRole",
        region = "us-west-2",
      })
      
      assert.equals("us-west-2", irsa_obj.region)
    end)
    
    it("should error when token_file is not provided", function()
      assert.has_error(function()
        irsa.new({
          role_arn = "arn:aws:iam::123456789012:role/TestRole",
        })
      end, "token_file must be specified or AWS_WEB_IDENTITY_TOKEN_FILE environment variable must be set")
    end)
    
    it("should error when token_file is empty string", function()
      assert.has_error(function()
        irsa.new({
          token_file = "",
          role_arn = "arn:aws:iam::123456789012:role/TestRole",
        })
      end, "token_file must be specified or AWS_WEB_IDENTITY_TOKEN_FILE environment variable must be set")
    end)
    
    it("should error when role_arn is not provided", function()
      assert.has_error(function()
        irsa.new({
          token_file = test_token_file,
        })
      end, "role_arn must be specified or AWS_ROLE_ARN environment variable must be set")
    end)
    
    it("should error when role_arn is empty string", function()
      assert.has_error(function()
        irsa.new({
          token_file = test_token_file,
          role_arn = "",
        })
      end, "role_arn must be specified or AWS_ROLE_ARN environment variable must be set")
    end)
    
    it("should handle all custom parameters", function()
      local irsa_obj = irsa.new({
        token_file = test_token_file,
        role_arn = "arn:aws:iam::123456789012:role/CustomRole",
        session_name = "my-custom-session",
        region = "eu-west-1",
      })
      
      assert.equals(test_token_file, irsa_obj.token_file)
      assert.equals("arn:aws:iam::123456789012:role/CustomRole", irsa_obj.role_arn)
      assert.equals("my-custom-session", irsa_obj.session_name)
      assert.equals("eu-west-1", irsa_obj.region)
    end)
  end)
  
  describe("IRSA:refresh", function()
    it("should successfully refresh credentials", function()
      local irsa_obj = irsa.new({
        token_file = test_token_file,
        role_arn = "arn:aws:iam::123456789012:role/TestRole",
      })
      
      local success, err = irsa_obj:refresh()
      
      assert.is_true(success)
      assert.is_nil(err)
    end)
    
    it("should return error when token file does not exist", function()
      local irsa_obj = irsa.new({
        token_file = "/non/existent/token/file",
        role_arn = "arn:aws:iam::123456789012:role/TestRole",
      })
      
      local success, err = irsa_obj:refresh()
      
      assert.is_nil(success)
      assert.is_not_nil(err)
      assert.is_true(err:match("Failed to open token file") ~= nil)
    end)
    
    it("should handle STS API errors", function()
      -- We need to reload the module with a different HTTP mock
      -- Save the current loaded module
      local current_irsa = irsa
      
      -- Create a mock that returns error
      package.loaded["resty.http"] = {
        new = function()
          return {
            set_timeout = function() end,
            request_uri = function()
              return {
                status = 403,
                body = "<Error><Code>AccessDenied</Code></Error>"
              }, nil
            end
          }
        end
      }
      
      package.loaded["kong.plugins.o11y-proxy.http_client"] = {
        new = function()
          return {
            set_timeout = function() end,
            request_uri = function()
              return {
                status = 403,
                body = "<Error><Code>AccessDenied</Code></Error>"
              }, nil
            end
          }
        end
      }
      
      -- Reload the module with error mock
      package.loaded["kong.plugins.o11y-proxy.irsa"] = nil
      local irsa_error = dofile(plugin_dir .. "irsa.lua")
      
      local irsa_obj = irsa_error.new({
        token_file = test_token_file,
        role_arn = "arn:aws:iam::123456789012:role/TestRole",
      })
      
      local success, err = irsa_obj:refresh()
      
      -- Restore original mock
      package.loaded["resty.http"] = {
        new = function()
          return {
            set_timeout = function() end,
            request_uri = function(self, url, opts)
              return mock_http_response, nil
            end
          }
        end
      }
      
      package.loaded["kong.plugins.o11y-proxy.http_client"] = {
        new = function()
          return {
            set_timeout = function() end,
            request_uri = function(self, url, opts)
              return mock_http_response, nil
            end
          }
        end
      }
      
      -- Restore original module
      package.loaded["kong.plugins.o11y-proxy.irsa"] = nil
      irsa = current_irsa
      
      assert.is_nil(success)
      assert.is_not_nil(err)
      assert.is_true(err:match("STS API returned error status") ~= nil)
    end)
  end)
  
  describe("IRSA:get_credentials", function()
    it("should get credentials successfully", function()
      local irsa_obj = irsa.new({
        token_file = test_token_file,
        role_arn = "arn:aws:iam::123456789012:role/TestRole",
      })
      
      -- Clear cache to force refresh
      irsa_obj:clear_cache()
      
      local credentials, err = irsa_obj:get_credentials()
      
      assert.is_nil(err)
      assert.is_not_nil(credentials)
      assert.is_not_nil(credentials.access_key_id)
      assert.is_not_nil(credentials.secret_access_key)
      assert.is_not_nil(credentials.session_token)
    end)
    
    it("should use cached credentials when valid", function()
      local irsa_obj = irsa.new({
        token_file = test_token_file,
        role_arn = "arn:aws:iam::123456789012:role/TestRole",
      })
      
      -- First call - should refresh
      local creds1, err1 = irsa_obj:get_credentials()
      assert.is_nil(err1)
      assert.is_not_nil(creds1)
      
      -- Second call - should use cache (no refresh)
      local creds2, err2 = irsa_obj:get_credentials()
      assert.is_nil(err2)
      assert.is_not_nil(creds2)
      
      -- Credentials should be the same
      assert.equals(creds1.access_key_id, creds2.access_key_id)
      assert.equals(creds1.secret_access_key, creds2.secret_access_key)
      assert.equals(creds1.session_token, creds2.session_token)
    end)
    
    it("should return error when refresh fails", function()
      local irsa_obj = irsa.new({
        token_file = "/non/existent/file",
        role_arn = "arn:aws:iam::123456789012:role/TestRole",
      })
      
      irsa_obj:clear_cache()
      
      local credentials, err = irsa_obj:get_credentials()
      
      assert.is_nil(credentials)
      assert.is_not_nil(err)
    end)
  end)
  
  describe("IRSA:clear_cache", function()
    it("should clear the credentials cache", function()
      local irsa_obj = irsa.new({
        token_file = test_token_file,
        role_arn = "arn:aws:iam::123456789012:role/TestRole",
      })
      
      -- Get credentials to populate cache
      local creds1, err1 = irsa_obj:get_credentials()
      assert.is_nil(err1)
      
      -- Clear cache
      assert.has_no_error(function()
        irsa_obj:clear_cache()
      end)
      
      -- Next get_credentials should refresh (we can't directly test this
      -- but at least verify clear_cache doesn't error)
      local creds2, err2 = irsa_obj:get_credentials()
      assert.is_nil(err2)
      assert.is_not_nil(creds2)
    end)
  end)
  
  describe("IRSA with different regions", function()
    it("should handle us-east-1 region correctly", function()
      local irsa_obj = irsa.new({
        token_file = test_token_file,
        role_arn = "arn:aws:iam::123456789012:role/TestRole",
        region = "us-east-1",
      })
      
      assert.equals("us-east-1", irsa_obj.region)
    end)
    
    it("should handle non-us-east-1 regions correctly", function()
      local irsa_obj = irsa.new({
        token_file = test_token_file,
        role_arn = "arn:aws:iam::123456789012:role/TestRole",
        region = "ap-northeast-1",
      })
      
      assert.equals("ap-northeast-1", irsa_obj.region)
    end)
  end)
end)
