--- Unit tests for resource_mapper.lua
-- Tests resource URI mapping for authorization

-- 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

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

describe("resource_mapper.lua", function()
  
  -- Mock kong.log for unit tests
  before_each(function()
    _G.kong = {
      log = {
        warn = function(msg) end,
        notice = function(msg) end,
        err = function(msg) end,
        debug = function(msg) end,
      }
    }
  end)
  
  after_each(function()
    _G.kong = nil
  end)
  
  describe("get_resource_uri_template", function()
    
    describe("Metrics API patterns", function()
      it("should match metrics API path", function()
        local template = resource_mapper.get_resource_uri_template("/12345/metrics/api/v1/query")
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", template)
      end)
      
      it("should match metrics API with query_range endpoint", function()
        local template = resource_mapper.get_resource_uri_template("/67890/metrics/api/v1/query_range")
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", template)
      end)
      
      it("should match metrics API with labels endpoint", function()
        local template = resource_mapper.get_resource_uri_template("/abc123/metrics/api/v1/labels")
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", template)
      end)
      
      it("should match metrics API with series endpoint", function()
        local template = resource_mapper.get_resource_uri_template("/cluster-456/metrics/api/v1/series")
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", template)
      end)
      
      it("should match metrics API with nested paths", function()
        local template = resource_mapper.get_resource_uri_template("/test-cluster/metrics/api/v1/label/job/values")
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", template)
      end)
    end)
    
    describe("Diagnosis API patterns", function()
      it("should match diagnosis API path", function()
        local template = resource_mapper.get_resource_uri_template("/98765/diagnosis/api/v1/query")
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/diagnosis", template)
      end)
      
      it("should match diagnosis API with different endpoints", function()
        local template = resource_mapper.get_resource_uri_template("/cluster999/diagnosis/api/v1/slow_queries")
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/diagnosis", template)
      end)
      
      it("should match diagnosis API with nested paths", function()
        local template = resource_mapper.get_resource_uri_template("/diag-cluster/diagnosis/api/v2/analysis/report")
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/diagnosis", template)
      end)
    end)
    
    describe("Unmatched patterns", function()
      it("should return nil for unknown API path", function()
        local template = resource_mapper.get_resource_uri_template("/cluster123/unknown/api/v1/query")
        
        assert.is_nil(template)
      end)
      
      it("should return nil for root path", function()
        local template = resource_mapper.get_resource_uri_template("/")
        
        assert.is_nil(template)
      end)
      
      it("should return nil for path without cluster_id prefix", function()
        local template = resource_mapper.get_resource_uri_template("/metrics/api/v1/query")
        
        assert.is_nil(template)
      end)
      
      it("should return nil for empty path", function()
        local template = resource_mapper.get_resource_uri_template("")
        
        assert.is_nil(template)
      end)
      
      it("should return nil for non-API path", function()
        local template = resource_mapper.get_resource_uri_template("/cluster456/status")
        
        assert.is_nil(template)
      end)
      
      it("should return nil for malformed path", function()
        local template = resource_mapper.get_resource_uri_template("/invalid/path/structure")
        
        assert.is_nil(template)
      end)
    end)
    
    describe("Edge cases", function()
      it("should handle path with query parameters", function()
        local template = resource_mapper.get_resource_uri_template("/cluster789/metrics/api/v1/query?start=123")
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", template)
      end)
      
      it("should handle path with trailing slash", function()
        local template = resource_mapper.get_resource_uri_template("/my-cluster/metrics/")
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", template)
      end)
      
      it("should handle path with multiple trailing slashes", function()
        local template = resource_mapper.get_resource_uri_template("/cluster-abc/metrics///")
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", template)
      end)
      
      it("should not match if cluster_id prefix is missing", function()
        local template = resource_mapper.get_resource_uri_template("/metrics/api/v1/query")
        
        assert.is_nil(template)
      end)
      
      it("should not match if pattern doesn't start with cluster ID", function()
        local template = resource_mapper.get_resource_uri_template("/api/cluster123/metrics/")
        
        assert.is_nil(template)
      end)
    end)
  end)
  
  describe("get_authz_uris", function()
    
    describe("Metrics API", function()
      it("should return template and filled URI for metrics with org and cluster IDs", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/test-cluster-1/metrics/api/v1/query",
          "12345",
          "67890"
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", resource_uri)
        assert.equals("o11y/orgs/12345/tidbs/67890/metrics", req_uri)
      end)
      
      it("should fill URI with empty strings when IDs are nil", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/cluster-nil-test/metrics/api/v1/query",
          nil,
          nil
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", resource_uri)
        assert.equals("o11y/orgs//tidbs//metrics", req_uri)
      end)
      
      it("should fill URI with partial IDs", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/partial-cluster/metrics/api/v1/query",
          "12345",
          nil
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", resource_uri)
        assert.equals("o11y/orgs/12345/tidbs//metrics", req_uri)
      end)
      
      it("should handle query_range endpoint", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/range-cluster/metrics/api/v1/query_range",
          "org123",
          "cluster456"
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", resource_uri)
        assert.equals("o11y/orgs/org123/tidbs/cluster456/metrics", req_uri)
      end)
      
      it("should handle labels endpoint", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/labels-cluster/metrics/api/v1/labels",
          "999",
          "888"
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", resource_uri)
        assert.equals("o11y/orgs/999/tidbs/888/metrics", req_uri)
      end)
    end)
    
    describe("Diagnosis API", function()
      it("should return template and filled URI for diagnosis with org and cluster IDs", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/diag-test-1/diagnosis/api/v1/query",
          "org_001",
          "cluster_002"
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/diagnosis", resource_uri)
        assert.equals("o11y/orgs/org_001/tidbs/cluster_002/diagnosis", req_uri)
      end)
      
      it("should handle slow_queries endpoint", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/slow-query-cluster/diagnosis/api/v1/slow_queries",
          "54321",
          "98765"
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/diagnosis", resource_uri)
        assert.equals("o11y/orgs/54321/tidbs/98765/diagnosis", req_uri)
      end)
      
      it("should handle nested diagnosis paths", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/nested-cluster/diagnosis/api/v2/analysis/report",
          "111",
          "222"
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/diagnosis", resource_uri)
        assert.equals("o11y/orgs/111/tidbs/222/diagnosis", req_uri)
      end)
    end)
    
    describe("Unmatched patterns", function()
      it("should return path as both URIs for unknown patterns", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/unknown/api/path",
          "12345",
          "67890"
        )
        
        assert.equals("/unknown/api/path", resource_uri)
        assert.equals("/unknown/api/path", req_uri)
      end)
      
      it("should return empty string for empty path", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "",
          "12345",
          "67890"
        )
        
        assert.equals("", resource_uri)
        assert.equals("", req_uri)
      end)
      
      it("should return root path when no pattern matches", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/",
          "org",
          "cluster"
        )
        
        assert.equals("/", resource_uri)
        assert.equals("/", req_uri)
      end)
      
      it("should not apply parameter substitution for unmatched paths", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/api/v1/custom",
          "12345",
          "67890"
        )
        
        assert.equals("/api/v1/custom", resource_uri)
        assert.equals("/api/v1/custom", req_uri)
        -- Ensure no {org_id} or {cluster_id} placeholders appear
        assert.is_nil(resource_uri:match("{org_id}"))
        assert.is_nil(req_uri:match("{cluster_id}"))
      end)
    end)
    
    describe("Parameter replacement", function()
      it("should replace all occurrences of placeholders", function()
        -- Test with metrics endpoint
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/param-test-cluster/metrics/api/v1/query",
          "ORG123",
          "CLUSTER456"
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", resource_uri)
        assert.equals("o11y/orgs/ORG123/tidbs/CLUSTER456/metrics", req_uri)
        
        -- Verify no placeholders remain in req_uri
        assert.is_nil(req_uri:match("{.*}"))
      end)
      
      it("should handle IDs with special characters", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/special-char-cluster/metrics/api/v1/query",
          "org-123-456",
          "cluster_789_abc"
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", resource_uri)
        assert.equals("o11y/orgs/org-123-456/tidbs/cluster_789_abc/metrics", req_uri)
      end)
      
      it("should handle numeric IDs", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/1234567890/diagnosis/api/v1/query",
          "1234567890",
          "9876543210"
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/diagnosis", resource_uri)
        assert.equals("o11y/orgs/1234567890/tidbs/9876543210/diagnosis", req_uri)
      end)
      
      it("should handle very long IDs", function()
        local long_org_id = string.rep("1234567890", 10)  -- 100 characters
        local long_cluster_id = string.rep("abcdefghij", 10)  -- 100 characters
        
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/very-long-cluster-id/metrics/api/v1/query",
          long_org_id,
          long_cluster_id
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", resource_uri)
        assert.is_true(req_uri:find(long_org_id, 1, true) ~= nil)
        assert.is_true(req_uri:find(long_cluster_id, 1, true) ~= nil)
      end)
      
      it("should preserve template when IDs contain braces", function()
        -- Edge case: IDs that look like placeholders
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/brace-test-cluster/metrics/api/v1/query",
          "{malicious}",
          "normal_id"
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", resource_uri)
        assert.equals("o11y/orgs/{malicious}/tidbs/normal_id/metrics", req_uri)
      end)
    end)
    
    describe("Real-world scenarios", function()
      it("should handle typical TiDB Cloud organization and cluster IDs", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/10315726917312357102/metrics/api/v1/query_range",
          "1372813089209241232",
          "10315726917312357102"
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", resource_uri)
        assert.equals("o11y/orgs/1372813089209241232/tidbs/10315726917312357102/metrics", req_uri)
      end)
      
      it("should handle diagnosis endpoint with real IDs", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/10558056491779918366/diagnosis/api/v1/slow_queries",
          "60205",
          "10558056491779918366"
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/diagnosis", resource_uri)
        assert.equals("o11y/orgs/60205/tidbs/10558056491779918366/diagnosis", req_uri)
      end)
      
      it("should handle metrics endpoint with query parameters in path", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/cluster888/metrics/api/v1/query?start=123&end=456",
          "org999",
          "cluster888"
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", resource_uri)
        assert.equals("o11y/orgs/org999/tidbs/cluster888/metrics", req_uri)
      end)
      
      it("should handle series endpoint for Prometheus queries", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/5678/metrics/api/v1/series",
          "1234",
          "5678"
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", resource_uri)
        assert.equals("o11y/orgs/1234/tidbs/5678/metrics", req_uri)
      end)
    end)
    
    describe("Consistency checks", function()
      it("should always return two values", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/consistency-cluster/metrics/api/v1/query",
          "org",
          "cluster"
        )
        
        assert.is_not_nil(resource_uri)
        assert.is_not_nil(req_uri)
      end)
      
      it("should return same template for different endpoints of same API", function()
        local template1 = resource_mapper.get_authz_uris(
          "/same-template-test/metrics/api/v1/query",
          "org",
          "cluster"
        )
        local template2 = resource_mapper.get_authz_uris(
          "/same-template-test/metrics/api/v1/query_range",
          "org",
          "cluster"
        )
        local template3 = resource_mapper.get_authz_uris(
          "/same-template-test/metrics/api/v1/labels",
          "org",
          "cluster"
        )
        
        assert.equals(template1, template2)
        assert.equals(template2, template3)
      end)
      
      it("should return different templates for different APIs", function()
        local metrics_template = resource_mapper.get_authz_uris(
          "/diff-template-test/metrics/api/v1/query",
          "org",
          "cluster"
        )
        local diagnosis_template = resource_mapper.get_authz_uris(
          "/diff-template-test/diagnosis/api/v1/query",
          "org",
          "cluster"
        )
        
        assert.is_not.equals(metrics_template, diagnosis_template)
      end)
      
      it("should maintain template format in resource_uri", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/format-test-cluster/metrics/api/v1/query",
          "12345",
          "67890"
        )
        
        -- resource_uri should still have placeholders
        assert.is_not_nil(resource_uri:match("{org_id}"))
        assert.is_not_nil(resource_uri:match("{cluster_id}"))
        
        -- req_uri should NOT have placeholders
        assert.is_nil(req_uri:match("{org_id}"))
        assert.is_nil(req_uri:match("{cluster_id}"))
      end)
      
      it("should produce same result for same inputs", function()
        local resource_uri1, req_uri1 = resource_mapper.get_authz_uris(
          "/same-result-test/metrics/api/v1/query",
          "test_org",
          "test_cluster"
        )
        local resource_uri2, req_uri2 = resource_mapper.get_authz_uris(
          "/same-result-test/metrics/api/v1/query",
          "test_org",
          "test_cluster"
        )
        
        assert.equals(resource_uri1, resource_uri2)
        assert.equals(req_uri1, req_uri2)
      end)
    end)
    
    describe("Empty and nil parameter handling", function()
      it("should handle empty string org_id", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/empty-org-test/metrics/api/v1/query",
          "",
          "cluster123"
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", resource_uri)
        assert.equals("o11y/orgs//tidbs/cluster123/metrics", req_uri)
      end)
      
      it("should handle empty string cluster_id", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/empty-cluster-test/metrics/api/v1/query",
          "org123",
          ""
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", resource_uri)
        assert.equals("o11y/orgs/org123/tidbs//metrics", req_uri)
      end)
      
      it("should handle both IDs as empty strings", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/both-empty-test/metrics/api/v1/query",
          "",
          ""
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", resource_uri)
        assert.equals("o11y/orgs//tidbs//metrics", req_uri)
      end)
      
      it("should handle whitespace-only IDs", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/whitespace-test/diagnosis/api/v1/query",
          "   ",
          "\t\n"
        )
        
        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/diagnosis", resource_uri)
        -- Whitespace should be preserved
        assert.is_true(req_uri:find("   ", 1, true) ~= nil)
      end)
    end)
  end)
end)
