--- Unit tests for parse_url function in handler.lua
-- Tests URL parsing functionality

-- 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,
    warn = function() end,
  },
  request = {
    get_header = function() return nil end,
    get_method = function() return "GET" end,
    get_raw_body = function() return "" end,
    get_headers = function() return {} end,
    get_path = function() return "/test/path" end,
    get_forwarded_prefix = function() return nil end,
    get_raw_query = function() return "" end,
  },
  response = {
    exit = function() end,
  },
  service = {
    set_target = function() end,
    request = {
      set_scheme = function() end,
      set_path = function() end,
      set_raw_query = function() end,
      set_header = function() 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 the assume module
package.loaded["kong.plugins.o11y-proxy.assume"] = {
  new = function()
    return {
      get_credentials = function()
        return {
          access_key_id = "test_key",
          secret_access_key = "test_secret",
          session_token = "test_token",
        }
      end
    }
  end
}

-- Mock the router module
package.loaded["kong.plugins.o11y-proxy.router"] = {
  query_target = function()
    return {
      dns_address = "https://test.execute-api.us-west-2.amazonaws.com/prod",
      access_principal = "arn:aws:iam::123456789012:role/TestRole",
      access_external_id = "test-external-id",
      logical_region_name = "aws-us-west-2",
    }, nil
  end
}

-- Mock the authn module
package.loaded["kong.plugins.o11y-proxy.authn"] = {
  authenticate = function()
    return {
      check_result = true,
      user_id = 12345,
    }, nil
  end
}

-- Mock the authz module
package.loaded["kong.plugins.o11y-proxy.authz"] = {
  check_permission = function()
    return {
      has_permission = true,
    }, nil
  end
}

-- Mock the resource_mapper module
package.loaded["kong.plugins.o11y-proxy.resource_mapper"] = {
  get_authz_uris = function()
    return "test_resource_uri", "test_req_uri"
  end,
  get_resource_uri_template = function()
    return "test_template"
  end
}

-- Mock the aws_signer module
package.loaded["kong.plugins.o11y-proxy.aws_signer"] = {
  sign_request = function()
    return {
      Authorization = "AWS4-HMAC-SHA256 ...",
      ["X-Amz-Date"] = "20240101T000000Z",
      Host = "test.amazonaws.com",
    }
  end
}

local handler = dofile(plugin_dir .. "handler.lua")
local parse_url = handler._parse_url
local resolve_sigv4_region = handler._resolve_sigv4_region

describe("handler.lua - parse_url", function()

  describe("Basic URL parsing", function()
    it("should parse simple HTTP URL without port", function()
      local scheme, host, port, path, query = parse_url("http://example.com")

      assert.equals("http", scheme)
      assert.equals("example.com", host)
      assert.equals(80, port)
      assert.equals("/", path)
      assert.is_nil(query)
    end)

    it("should parse simple HTTPS URL without port", function()
      local scheme, host, port, path, query = parse_url("https://example.com")

      assert.equals("https", scheme)
      assert.equals("example.com", host)
      assert.equals(443, port)
      assert.equals("/", path)
      assert.is_nil(query)
    end)

    it("should parse HTTP URL with custom port", function()
      local scheme, host, port, path, query = parse_url("http://example.com:8080")

      assert.equals("http", scheme)
      assert.equals("example.com", host)
      assert.equals(8080, port)
      assert.equals("/", path)
      assert.is_nil(query)
    end)

    it("should parse HTTPS URL with custom port", function()
      local scheme, host, port, path, query = parse_url("https://example.com:8443")

      assert.equals("https", scheme)
      assert.equals("example.com", host)
      assert.equals(8443, port)
      assert.equals("/", path)
      assert.is_nil(query)
    end)
  end)

  describe("URL with path", function()
    it("should parse URL with simple path", function()
      local scheme, host, port, path, query = parse_url("https://example.com/api/v1")

      assert.equals("https", scheme)
      assert.equals("example.com", host)
      assert.equals(443, port)
      assert.equals("/api/v1", path)
      assert.is_nil(query)
    end)

    it("should parse URL with root path", function()
      local scheme, host, port, path, query = parse_url("https://example.com/")

      assert.equals("https", scheme)
      assert.equals("example.com", host)
      assert.equals(443, port)
      assert.equals("/", path)
      assert.is_nil(query)
    end)

    it("should parse URL with complex path", function()
      local scheme, host, port, path, query = parse_url("https://example.com/api/v1/users/123/profile")

      assert.equals("https", scheme)
      assert.equals("example.com", host)
      assert.equals(443, port)
      assert.equals("/api/v1/users/123/profile", path)
      assert.is_nil(query)
    end)

    it("should parse URL with path and custom port", function()
      local scheme, host, port, path, query = parse_url("http://example.com:8080/api/v1")

      assert.equals("http", scheme)
      assert.equals("example.com", host)
      assert.equals(8080, port)
      assert.equals("/api/v1", path)
      assert.is_nil(query)
    end)
  end)

  describe("URL with query string", function()
    it("should parse URL with simple query", function()
      local scheme, host, port, path, query = parse_url("https://example.com/api?key=value")

      assert.equals("https", scheme)
      assert.equals("example.com", host)
      assert.equals(443, port)
      assert.equals("/api", path)
      assert.equals("key=value", query)
    end)

    it("should parse URL with multiple query parameters", function()
      local scheme, host, port, path, query = parse_url("https://example.com/api?key1=value1&key2=value2")

      assert.equals("https", scheme)
      assert.equals("example.com", host)
      assert.equals(443, port)
      assert.equals("/api", path)
      assert.equals("key1=value1&key2=value2", query)
    end)

    it("should parse URL with query but no path", function()
      local scheme, host, port, path, query = parse_url("https://example.com?key=value")

      -- Based on current implementation, query without path is parsed as part of host
      -- This is a limitation of the current regex pattern
      assert.equals("https", scheme)
      assert.equals("example.com?key=value", host)  -- Query becomes part of host
      assert.equals(443, port)
      assert.equals("/", path)
      assert.is_nil(query)
    end)

    it("should handle empty query string", function()
      local scheme, host, port, path, query = parse_url("https://example.com/api?")

      assert.equals("https", scheme)
      assert.equals("example.com", host)
      assert.equals(443, port)
      assert.equals("/api", path)
      assert.is_nil(query)  -- Empty query should be nil
    end)

    it("should parse URL with port, path, and query", function()
      local scheme, host, port, path, query = parse_url("http://example.com:8080/api/v1?key=value&foo=bar")

      assert.equals("http", scheme)
      assert.equals("example.com", host)
      assert.equals(8080, port)
      assert.equals("/api/v1", path)
      assert.equals("key=value&foo=bar", query)
    end)
  end)

  describe("AWS API Gateway URLs", function()
    it("should parse API Gateway URL", function()
      local scheme, host, port, path, query = parse_url("https://abc123.execute-api.us-east-1.amazonaws.com/prod/users")

      assert.equals("https", scheme)
      assert.equals("abc123.execute-api.us-east-1.amazonaws.com", host)
      assert.equals(443, port)
      assert.equals("/prod/users", path)
      assert.is_nil(query)
    end)

    it("should parse API Gateway URL with query", function()
      local scheme, host, port, path, query = parse_url("https://xyz789.execute-api.ap-northeast-1.amazonaws.com/dev/items?limit=10")

      assert.equals("https", scheme)
      assert.equals("xyz789.execute-api.ap-northeast-1.amazonaws.com", host)
      assert.equals(443, port)
      assert.equals("/dev/items", path)
      assert.equals("limit=10", query)
    end)
  end)

  describe("Complex URLs", function()
    it("should parse URL with special characters in path", function()
      local scheme, host, port, path, query = parse_url("https://example.com/api/users/john-doe_123")

      assert.equals("https", scheme)
      assert.equals("example.com", host)
      assert.equals(443, port)
      assert.equals("/api/users/john-doe_123", path)
      assert.is_nil(query)
    end)

    it("should parse URL with subdomain", function()
      local scheme, host, port, path, query = parse_url("https://api.v2.example.com/endpoint")

      assert.equals("https", scheme)
      assert.equals("api.v2.example.com", host)
      assert.equals(443, port)
      assert.equals("/endpoint", path)
      assert.is_nil(query)
    end)

    it("should parse localhost URL", function()
      local scheme, host, port, path, query = parse_url("http://localhost:3000/api")

      assert.equals("http", scheme)
      assert.equals("localhost", host)
      assert.equals(3000, port)
      assert.equals("/api", path)
      assert.is_nil(query)
    end)

    it("should parse IP address URL", function()
      local scheme, host, port, path, query = parse_url("http://192.168.1.100:8080/api")

      assert.equals("http", scheme)
      assert.equals("192.168.1.100", host)
      assert.equals(8080, port)
      assert.equals("/api", path)
      assert.is_nil(query)
    end)
  end)

  describe("Error handling", function()
    it("should return error for nil URL", function()
      local scheme, err = parse_url(nil)

      assert.is_nil(scheme)
      assert.equals("URL cannot be empty", err)
    end)

    it("should return error for empty URL", function()
      local scheme, err = parse_url("")

      assert.is_nil(scheme)
      assert.equals("URL cannot be empty", err)
    end)

    it("should return error for URL without scheme", function()
      local scheme, err = parse_url("example.com/api")

      assert.is_nil(scheme)
      assert.equals("Invalid URL format. Expected: scheme://host[:port][/path][?query]", err)
    end)

    it("should return error for invalid scheme separator", function()
      local scheme, err = parse_url("https:/example.com")

      assert.is_nil(scheme)
      assert.equals("Invalid URL format. Expected: scheme://host[:port][/path][?query]", err)
    end)

    it("should return error for URL with only scheme", function()
      local scheme, err = parse_url("https://")

      assert.is_nil(scheme)
      -- Empty host after :// is not matched by the pattern
      assert.equals("Invalid URL format. Expected: scheme://host[:port][/path][?query]", err)
    end)

    it("should return error for invalid port (non-numeric)", function()
      local scheme, err = parse_url("http://example.com:abc")

      assert.is_nil(scheme)
      assert.equals("Port must be a valid port number (1-65535)", err)
    end)

    it("should return error for invalid port (too large)", function()
      local scheme, err = parse_url("http://example.com:99999")

      assert.is_nil(scheme)
      assert.equals("Port must be a valid port number (1-65535)", err)
    end)

    it("should return error for invalid port (zero)", function()
      local scheme, err = parse_url("http://example.com:0")

      assert.is_nil(scheme)
      assert.equals("Port must be a valid port number (1-65535)", err)
    end)

    it("should return error for invalid port (negative)", function()
      local scheme, err = parse_url("http://example.com:-1")

      assert.is_nil(scheme)
      assert.equals("Port must be a valid port number (1-65535)", err)
    end)
  end)

  describe("Edge cases", function()
    it("should handle URL with multiple slashes in path", function()
      local scheme, host, port, path, query = parse_url("https://example.com//api//v1//users")

      assert.equals("https", scheme)
      assert.equals("example.com", host)
      assert.equals(443, port)
      assert.equals("//api//v1//users", path)
      assert.is_nil(query)
    end)

    it("should handle URL with trailing slash in path", function()
      local scheme, host, port, path, query = parse_url("https://example.com/api/")

      assert.equals("https", scheme)
      assert.equals("example.com", host)
      assert.equals(443, port)
      assert.equals("/api/", path)
      assert.is_nil(query)
    end)

    it("should handle standard HTTP port 80 explicitly", function()
      local scheme, host, port, path, query = parse_url("http://example.com:80")

      assert.equals("http", scheme)
      assert.equals("example.com", host)
      assert.equals(80, port)
      assert.equals("/", path)
      assert.is_nil(query)
    end)

    it("should handle standard HTTPS port 443 explicitly", function()
      local scheme, host, port, path, query = parse_url("https://example.com:443")

      assert.equals("https", scheme)
      assert.equals("example.com", host)
      assert.equals(443, port)
      assert.equals("/", path)
      assert.is_nil(query)
    end)

    it("should handle URL with numeric path segments", function()
      local scheme, host, port, path, query = parse_url("https://example.com/api/123/456")

      assert.equals("https", scheme)
      assert.equals("example.com", host)
      assert.equals(443, port)
      assert.equals("/api/123/456", path)
      assert.is_nil(query)
    end)

    it("should handle URL with dots in hostname", function()
      local scheme, host, port, path, query = parse_url("https://api.v1.example.co.uk/endpoint")

      assert.equals("https", scheme)
      assert.equals("api.v1.example.co.uk", host)
      assert.equals(443, port)
      assert.equals("/endpoint", path)
      assert.is_nil(query)
    end)

    it("should handle URL with hyphen in hostname", function()
      local scheme, host, port, path, query = parse_url("https://my-api-server.example.com/api")

      assert.equals("https", scheme)
      assert.equals("my-api-server.example.com", host)
      assert.equals(443, port)
      assert.equals("/api", path)
      assert.is_nil(query)
    end)
  end)

  describe("Real-world examples", function()
    it("should parse typical REST API endpoint", function()
      local scheme, host, port, path, query = parse_url("https://api.example.com/v1/users/123?include=profile&fields=name,email")

      assert.equals("https", scheme)
      assert.equals("api.example.com", host)
      assert.equals(443, port)
      assert.equals("/v1/users/123", path)
      assert.equals("include=profile&fields=name,email", query)
    end)

    it("should parse S3-style URL", function()
      local scheme, host, port, path, query = parse_url("https://my-bucket.s3.us-west-2.amazonaws.com/path/to/object.json")

      assert.equals("https", scheme)
      assert.equals("my-bucket.s3.us-west-2.amazonaws.com", host)
      assert.equals(443, port)
      assert.equals("/path/to/object.json", path)
      assert.is_nil(query)
    end)

    it("should parse URL with encoded characters in query", function()
      local scheme, host, port, path, query = parse_url("https://example.com/search?q=hello%20world&page=1")

      assert.equals("https", scheme)
      assert.equals("example.com", host)
      assert.equals(443, port)
      assert.equals("/search", path)
      assert.equals("q=hello%20world&page=1", query)
    end)
  end)
end)

describe("handler.lua - resolve_sigv4_region", function()
  it("should prefer region parsed from execute-api host over config region", function()
    local region = resolve_sigv4_region({ region = "us-west-2" }, "abc123.execute-api.us-east-1.amazonaws.com")

    assert.equals("us-east-1", region)
  end)

  it("should fall back to config region when host does not contain execute-api region", function()
    local region = resolve_sigv4_region({ region = "eu-central-1" }, "internal-alb.example.com")

    assert.equals("eu-central-1", region)
  end)

  it("should default to us-west-2 when neither host nor config provides region", function()
    local region = resolve_sigv4_region({}, "internal-alb.example.com")

    assert.equals("us-west-2", region)
  end)

  it("should default to us-west-2 when config is nil and host is empty", function()
    local region = resolve_sigv4_region(nil, nil)

    assert.equals("us-west-2", region)
  end)
end)
