--- 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,
      },
      ctx = {
        shared = {}
      }
    }
  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", "nextgen")

        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", template)
      end)

      it("should match metrics API path with byoc biz_type", function()
        local template = resource_mapper.get_resource_uri_template("/12345/metrics/api/v1/query", "byoc")

        assert.equals("o11y/orgs/{org_id}/byoc/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", "nextgen")

        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", "nextgen")

        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", "nextgen")

        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", "nextgen")

        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", "nextgen")

        assert.equals("o11y/orgs/{org_id}/tidbs/{cluster_id}/diagnosis", template)
      end)

      it("should match diagnosis API path with byoc biz_type", function()
        local template = resource_mapper.get_resource_uri_template("/98765/diagnosis/api/v1/query", "byoc")

        assert.equals("o11y/orgs/{org_id}/byoc/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", "nextgen")

        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", "nextgen")

        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", "nextgen")

        assert.is_nil(template)
      end)

      it("should return nil for unsupported biz_type", function()
        local template = resource_mapper.get_resource_uri_template("/12345/metrics/api/v1/query", "unsupported_type")

        assert.is_nil(template)
      end)

      it("should return nil for root path", function()
        local template = resource_mapper.get_resource_uri_template("/", "nextgen")

        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", "nextgen")

        assert.is_nil(template)
      end)

      it("should return nil for empty path", function()
        local template = resource_mapper.get_resource_uri_template("", "nextgen")

        assert.is_nil(template)
      end)

      it("should return nil for non-API path", function()
        local template = resource_mapper.get_resource_uri_template("/cluster456/status", "nextgen")

        assert.is_nil(template)
      end)

      it("should return nil for malformed path", function()
        local template = resource_mapper.get_resource_uri_template("/invalid/path/structure", "nextgen")

        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", "nextgen")

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

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

        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", "nextgen")

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

        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",
          "nextgen",
          "12345",
          nil,
          "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 return template and filled URI for metrics with byoc biz_type", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/test-cluster-1/metrics/api/v1/query",
          "byoc",
          "12345",
          nil,
          "67890"
        )

        assert.equals("o11y/orgs/{org_id}/byoc/metrics", resource_uri)
        assert.equals("o11y/orgs/12345/byoc/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",
          "nextgen",
          nil,
          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",
          "nextgen",
          "12345",
          nil,
          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",
          "nextgen",
          "org123",
          nil,
          "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",
          "nextgen",
          "999",
          nil,
          "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",
          "nextgen",
          "org_001",
          nil,
          "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 return template and filled URI for diagnosis with byoc biz_type", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/diag-test-1/diagnosis/api/v1/query",
          "byoc",
          "org_001",
          nil,
          "cluster_002"
        )

        assert.equals("o11y/orgs/{org_id}/byoc/diagnosis", resource_uri)
        assert.equals("o11y/orgs/org_001/byoc/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",
          "nextgen",
          "54321",
          nil,
          "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",
          "nextgen",
          "111",
          nil,
          "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",
          "nextgen",
          "12345",
          nil,
          "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(
          "",
          "nextgen",
          "12345",
          nil,
          "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(
          "/",
          "nextgen",
          "org",
          nil,
          "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",
          "nextgen",
          "12345",
          nil,
          "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",
          "nextgen",
          "ORG123",
          nil,
          "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",
          "nextgen",
          "org-123-456",
          nil,
          "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",
          "nextgen",
          "1234567890",
          nil,
          "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",
          "nextgen",
          long_org_id,
          nil,
          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",
          "nextgen",
          "{malicious}",
          nil,
          "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",
          "nextgen",
          "1372813089209241232",
          nil,
          "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",
          "nextgen",
          "60205",
          nil,
          "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",
          "nextgen",
          "org999",
          nil,
          "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",
          "nextgen",
          "1234",
          nil,
          "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",
          "nextgen",
          "org",
          nil,
          "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",
          "nextgen",
          "org",
          nil,
          "cluster"
        )
        local template2 = resource_mapper.get_authz_uris(
          "/same-template-test/metrics/api/v1/query_range",
          "nextgen",
          "org",
          nil,
          "cluster"
        )
        local template3 = resource_mapper.get_authz_uris(
          "/same-template-test/metrics/api/v1/labels",
          "nextgen",
          "org",
          nil,
          "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",
          "nextgen",
          "org",
          nil,
          "cluster"
        )
        local diagnosis_template = resource_mapper.get_authz_uris(
          "/diff-template-test/diagnosis/api/v1/query",
          "nextgen",
          "org",
          nil,
          "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",
          "nextgen",
          "12345",
          nil,
          "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",
          "nextgen",
          "test_org",
          nil,
          "test_cluster"
        )
        local resource_uri2, req_uri2 = resource_mapper.get_authz_uris(
          "/same-result-test/metrics/api/v1/query",
          "nextgen",
          "test_org",
          nil,
          "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",
          "nextgen",
          "",
          nil,
          "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",
          "nextgen",
          "org123",
          nil,
          ""
        )

        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",
          "nextgen",
          "",
          nil,
          ""
        )

        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",
          "nextgen",
          "   ",
          nil,
          "\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)

      it("should fill project segment when project_id is provided for tidbcloud biz_type", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/project-filled/metrics/api/v1/query",
          "tidbcloud",
          "org-1",
          "proj-2",
          "cluster-3"
        )

        assert.equals("o11y/orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/metrics", resource_uri)
        assert.equals("o11y/orgs/org-1/projects/proj-2/tidbs/cluster-3/metrics", req_uri)
      end)

      it("should leave project segment empty when project_id is nil for tidbcloud biz_type", function()
        local resource_uri, req_uri = resource_mapper.get_authz_uris(
          "/project-empty/diagnosis/api/v1/query",
          "tidbcloud",
          "org-1",
          nil,
          "cluster-3"
        )

        assert.equals("o11y/orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/diagnosis", resource_uri)
        assert.equals("o11y/orgs/org-1/projects//tidbs/cluster-3/diagnosis", req_uri)
      end)
    end)
  end)

  describe("BYOC org-scoped mapping", function()

    it("maps byoc metrics to org-scoped resource", function()
      local tmpl = resource_mapper.get_resource_uri_template("/some-uuid/metrics/api/v1/query_range", "byoc")
      assert.are.equal("o11y/orgs/{org_id}/byoc/metrics", tmpl)
    end)

    it("maps dev-byoc metrics to org-scoped resource", function()
      local tmpl = resource_mapper.get_resource_uri_template("/some-uuid/metrics/api/v1/query_range", "dev-byoc")
      assert.are.equal("o11y/orgs/{org_id}/byoc/metrics", tmpl)
    end)

    it("maps byoc diagnosis to org-scoped resource", function()
      local tmpl = resource_mapper.get_resource_uri_template("/some-uuid/diagnosis/foo", "byoc")
      assert.are.equal("o11y/orgs/{org_id}/byoc/diagnosis", tmpl)
    end)

    it("keeps non-byoc nextgen metrics cluster-scoped", function()
      local tmpl = resource_mapper.get_resource_uri_template("/some-uuid/metrics/api/v1/query_range", "tidbcloud-nextgen")
      assert.are.equal("o11y/orgs/{org_id}/tidbs/{cluster_id}/metrics", tmpl)
    end)

    it("fills org-scoped byoc req_uri when cluster id is nil", function()
      local _, req_uri = resource_mapper.get_authz_uris("/some-uuid/metrics/x", "byoc", "12345", "", nil)
      assert.are.equal("o11y/orgs/12345/byoc/metrics", req_uri)
    end)
  end)
end)
