--- Unit tests for pure functions in irsa.lua
-- These tests focus on internal functions without external dependencies

-- 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/$", "")

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

_G.ngx = {
  escape_uri = function(str)
    -- Simple URL encode implementation for testing
    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
}

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

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

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

package.loaded["kong.plugins.o11y-proxy.utils"] = {
  parse_iso8601 = function(date_str)
    -- Simple mock that returns a future timestamp for valid dates
    if date_str and date_str ~= "" then
      return os.time() + 3600  -- 1 hour in the future
    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
_G.os = {
  getenv = function(name)
    return nil  -- Return nil for all env vars in tests
  end,
  time = original_os_time,
  date = original_os_date,
  remove = original_os_remove,
}

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

describe("irsa.lua - pure functions", function()
  
  describe("read_token_file", function()
    local read_token_file = irsa._read_token_file
    local test_token_file = "/tmp/test_token_file_" .. os.time()
    
    before_each(function()
      -- Create a test token file
      local file = io.open(test_token_file, "w")
      if file then
        file:write("test-web-identity-token-content")
        file:close()
      end
    end)
    
    after_each(function()
      -- Clean up test file
      os.remove(test_token_file)
    end)
    
    it("should read token from valid file", function()
      local token, err = read_token_file(test_token_file)
      
      assert.is_nil(err)
      assert.equals("test-web-identity-token-content", token)
    end)
    
    it("should return error for non-existent file", function()
      local token, err = read_token_file("/non/existent/file")
      
      assert.is_nil(token)
      assert.is_not_nil(err)
      assert.is_true(err:match("Failed to open token file") ~= nil)
    end)
    
    it("should return error for empty file", function()
      local empty_file = "/tmp/empty_token_file_" .. os.time()
      local file = io.open(empty_file, "w")
      file:close()
      
      local token, err = read_token_file(empty_file)
      
      os.remove(empty_file)
      
      assert.is_nil(token)
      assert.equals("Token file is empty", err)
    end)
    
    it("should handle file with whitespace only", function()
      local whitespace_file = "/tmp/whitespace_token_file_" .. os.time()
      local file = io.open(whitespace_file, "w")
      file:write("   \n\t\n   ")
      file:close()
      
      local token, err = read_token_file(whitespace_file)
      
      os.remove(whitespace_file)
      
      -- Should read the whitespace content (not treated as empty)
      assert.is_not_nil(token)
      assert.is_nil(err)
    end)
    
    it("should handle large token file", function()
      local large_file = "/tmp/large_token_file_" .. os.time()
      local file = io.open(large_file, "w")
      local large_token = string.rep("A", 10000)
      file:write(large_token)
      file:close()
      
      local token, err = read_token_file(large_file)
      
      os.remove(large_file)
      
      assert.is_nil(err)
      assert.equals(large_token, token)
    end)
  end)
  
  describe("parse_sts_response", function()
    local parse_sts_response = irsa._parse_sts_response
    
    it("should parse valid STS AssumeRoleWithWebIdentity XML response", function()
      local xml_body = [[
        <AssumeRoleWithWebIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
          <AssumeRoleWithWebIdentityResult>
            <Credentials>
              <AccessKeyId>ASIATESTACCESSKEY</AccessKeyId>
              <SecretAccessKey>TestSecretKey123456789</SecretAccessKey>
              <SessionToken>TestSessionToken123456789</SessionToken>
              <Expiration>2024-01-01T12:00:00Z</Expiration>
            </Credentials>
          </AssumeRoleWithWebIdentityResult>
        </AssumeRoleWithWebIdentityResponse>
      ]]
      
      local credentials, err = parse_sts_response(xml_body)
      
      assert.is_nil(err)
      assert.is_not_nil(credentials)
      assert.equals("ASIATESTACCESSKEY", credentials.access_key_id)
      assert.equals("TestSecretKey123456789", credentials.secret_access_key)
      assert.equals("TestSessionToken123456789", credentials.session_token)
      assert.equals("2024-01-01T12:00:00Z", credentials.expiration)
    end)
    
    it("should return error for nil xml_body", function()
      local credentials, err = parse_sts_response(nil)
      
      assert.is_nil(credentials)
      assert.equals("Empty response body", err)
    end)
    
    it("should return error for empty xml_body", function()
      local credentials, err = parse_sts_response("")
      
      assert.is_nil(credentials)
      assert.equals("Failed to parse credentials from STS response", err)
    end)
    
    it("should return error when AccessKeyId is missing", function()
      local xml_body = [[
        <AssumeRoleWithWebIdentityResponse>
          <AssumeRoleWithWebIdentityResult>
            <Credentials>
              <SecretAccessKey>TestSecretKey</SecretAccessKey>
              <SessionToken>TestSessionToken</SessionToken>
              <Expiration>2024-01-01T12:00:00Z</Expiration>
            </Credentials>
          </AssumeRoleWithWebIdentityResult>
        </AssumeRoleWithWebIdentityResponse>
      ]]
      
      local credentials, err = parse_sts_response(xml_body)
      
      assert.is_nil(credentials)
      assert.equals("Failed to parse credentials from STS response", err)
    end)
    
    it("should return error when SecretAccessKey is missing", function()
      local xml_body = [[
        <AssumeRoleWithWebIdentityResponse>
          <AssumeRoleWithWebIdentityResult>
            <Credentials>
              <AccessKeyId>ASIATESTACCESSKEY</AccessKeyId>
              <SessionToken>TestSessionToken</SessionToken>
              <Expiration>2024-01-01T12:00:00Z</Expiration>
            </Credentials>
          </AssumeRoleWithWebIdentityResult>
        </AssumeRoleWithWebIdentityResponse>
      ]]
      
      local credentials, err = parse_sts_response(xml_body)
      
      assert.is_nil(credentials)
      assert.equals("Failed to parse credentials from STS response", err)
    end)
    
    it("should return error when SessionToken is missing", function()
      local xml_body = [[
        <AssumeRoleWithWebIdentityResponse>
          <AssumeRoleWithWebIdentityResult>
            <Credentials>
              <AccessKeyId>ASIATESTACCESSKEY</AccessKeyId>
              <SecretAccessKey>TestSecretKey</SecretAccessKey>
              <Expiration>2024-01-01T12:00:00Z</Expiration>
            </Credentials>
          </AssumeRoleWithWebIdentityResult>
        </AssumeRoleWithWebIdentityResponse>
      ]]
      
      local credentials, err = parse_sts_response(xml_body)
      
      assert.is_nil(credentials)
      assert.equals("Failed to parse credentials from STS response", err)
    end)
    
    it("should parse response even when Expiration is missing", function()
      local xml_body = [[
        <AssumeRoleWithWebIdentityResponse>
          <AssumeRoleWithWebIdentityResult>
            <Credentials>
              <AccessKeyId>ASIATESTACCESSKEY</AccessKeyId>
              <SecretAccessKey>TestSecretKey</SecretAccessKey>
              <SessionToken>TestSessionToken</SessionToken>
            </Credentials>
          </AssumeRoleWithWebIdentityResult>
        </AssumeRoleWithWebIdentityResponse>
      ]]
      
      local credentials, err = parse_sts_response(xml_body)
      
      assert.is_nil(err)
      assert.is_not_nil(credentials)
      assert.equals("ASIATESTACCESSKEY", credentials.access_key_id)
      assert.equals("TestSecretKey", credentials.secret_access_key)
      assert.equals("TestSessionToken", credentials.session_token)
      assert.is_nil(credentials.expiration)
    end)
    
    it("should handle XML with extra whitespace", function()
      local xml_body = [[
        <AssumeRoleWithWebIdentityResponse>
          <AssumeRoleWithWebIdentityResult>
            <Credentials>
              <AccessKeyId>  ASIATESTACCESSKEY  </AccessKeyId>
              <SecretAccessKey>  TestSecretKey  </SecretAccessKey>
              <SessionToken>  TestSessionToken  </SessionToken>
              <Expiration>  2024-01-01T12:00:00Z  </Expiration>
            </Credentials>
          </AssumeRoleWithWebIdentityResult>
        </AssumeRoleWithWebIdentityResponse>
      ]]
      
      local credentials, err = parse_sts_response(xml_body)
      
      assert.is_nil(err)
      assert.is_not_nil(credentials)
      -- Pattern matching will include whitespace
      assert.equals("  ASIATESTACCESSKEY  ", credentials.access_key_id)
    end)
    
    it("should handle credentials with special characters", function()
      local xml_body = [[
        <AssumeRoleWithWebIdentityResponse>
          <AssumeRoleWithWebIdentityResult>
            <Credentials>
              <AccessKeyId>ASIA+TEST/KEY=</AccessKeyId>
              <SecretAccessKey>Secret+Key/123=</SecretAccessKey>
              <SessionToken>Token+123/ABC=</SessionToken>
              <Expiration>2024-01-01T12:00:00Z</Expiration>
            </Credentials>
          </AssumeRoleWithWebIdentityResult>
        </AssumeRoleWithWebIdentityResponse>
      ]]
      
      local credentials, err = parse_sts_response(xml_body)
      
      assert.is_nil(err)
      assert.is_not_nil(credentials)
      assert.equals("ASIA+TEST/KEY=", credentials.access_key_id)
      assert.equals("Secret+Key/123=", credentials.secret_access_key)
      assert.equals("Token+123/ABC=", credentials.session_token)
    end)
    
    it("should handle malformed XML gracefully", function()
      local xml_body = "<InvalidXML><NoCredentials></InvalidXML>"
      
      local credentials, err = parse_sts_response(xml_body)
      
      assert.is_nil(credentials)
      assert.equals("Failed to parse credentials from STS response", err)
    end)
    
    it("should handle partial XML tags", function()
      local xml_body = [[
        <AssumeRoleWithWebIdentityResponse>
          <Credentials>
            <AccessKeyId>ASIATESTACCESSKEY
      ]]
      
      local credentials, err = parse_sts_response(xml_body)
      
      assert.is_nil(credentials)
      assert.equals("Failed to parse credentials from STS response", err)
    end)
  end)
  
  describe("is_cache_valid", function()
    local is_cache_valid = irsa._is_cache_valid
    
    -- Note: This function uses module-level credentials_cache
    -- We need to test via IRSA instance methods that manipulate the cache
    
    it("should return false when cache is empty", function()
      -- Create instance and clear cache
      local instance = irsa.new({
        token_file = "/tmp/test_token",
        role_arn = "arn:aws:iam::123456789012:role/TestRole"
      })
      instance:clear_cache()
      
      local is_valid = is_cache_valid()
      assert.is_false(is_valid)
    end)
  end)
end)
