package server

import (
	"context"
	"errors"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/pingcap/agent-git-service/config"
	"github.com/pingcap/agent-git-service/internal/service"
	"github.com/pingcap/agent-git-service/internal/tenant"
)

func TestCanonicalizeTenantRootsRejectsSymlinkedOverlap(t *testing.T) {
	base := t.TempDir()
	actual := filepath.Join(base, "actual")
	if err := os.MkdirAll(actual, 0o750); err != nil {
		t.Fatalf("mkdir actual root: %v", err)
	}
	alias := filepath.Join(base, "alias")
	if err := os.Symlink(actual, alias); err != nil {
		t.Fatalf("symlink root: %v", err)
	}

	_, err := canonicalizeTenantRoots([]config.TenantConfig{
		{ID: "tenant-a", GitRepoDir: alias},
		{ID: "tenant-b", GitRepoDir: filepath.Join(actual, "nested")},
	})
	if err == nil || !strings.Contains(err.Error(), "overlap after canonicalization") {
		t.Fatalf("canonicalizeTenantRoots error = %v, want canonical overlap", err)
	}
}

func TestCanonicalizeTenantRootsKeepsDisjointCanonicalPaths(t *testing.T) {
	base := t.TempDir()
	prepared, err := canonicalizeTenantRoots([]config.TenantConfig{
		{ID: "tenant-a", GitRepoDir: filepath.Join(base, "a")},
		{ID: "tenant-b", GitRepoDir: filepath.Join(base, "b")},
	})
	if err != nil {
		t.Fatalf("canonicalizeTenantRoots: %v", err)
	}
	if len(prepared) != 2 || prepared[0].GitRepoDir == prepared[1].GitRepoDir {
		t.Fatalf("prepared roots = %#v", prepared)
	}
	for _, value := range prepared {
		if !filepath.IsAbs(value.GitRepoDir) {
			t.Fatalf("root is not absolute: %q", value.GitRepoDir)
		}
	}
}

func TestCanonicalizeTenantRootsDoesNotTouchDisabledTenantStorage(t *testing.T) {
	disabledRoot := filepath.Join(t.TempDir(), "disabled", "must-not-exist")
	prepared, err := canonicalizeTenantRoots([]config.TenantConfig{
		{ID: "tenant-active", GitRepoDir: t.TempDir()},
		{ID: "tenant-disabled", Disabled: true, GitRepoDir: disabledRoot},
	})
	if err != nil {
		t.Fatalf("canonicalizeTenantRoots: %v", err)
	}
	if prepared[1].GitRepoDir != disabledRoot {
		t.Fatalf("disabled root changed from %q to %q", disabledRoot, prepared[1].GitRepoDir)
	}
	if _, err := os.Stat(disabledRoot); !errors.Is(err, os.ErrNotExist) {
		t.Fatalf("disabled root was touched: %v", err)
	}
}

func TestTenantRegistryRejectsContextPolicyMismatch(t *testing.T) {
	info := tenant.Info{
		ID:               "tenant-a",
		CanonicalBaseURL: "https://git.tenant-a.example",
	}
	resources := &service.TenantResources{Info: info}
	registry := &tenantRegistry{
		byID: map[tenant.ID]*service.TenantResources{
			info.ID: resources,
		},
	}

	resolved, err := registry.tenantForContext(tenant.WithInfo(context.Background(), info))
	if err != nil {
		t.Fatalf("tenantForContext with registry policy: %v", err)
	}
	if resolved != resources {
		t.Fatalf("tenantForContext returned %p, want %p", resolved, resources)
	}

	forged := info
	forged.CanonicalBaseURL = "https://attacker.example"
	_, err = registry.tenantForContext(tenant.WithInfo(context.Background(), forged))
	if !errors.Is(err, service.ErrTenantResourceMismatch) {
		t.Fatalf("tenantForContext forged policy error = %v, want %v", err, service.ErrTenantResourceMismatch)
	}
}
