package tenant_test

import (
	"context"
	"errors"
	"testing"

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

func TestParseIDNormalizesConfiguredIdentifier(t *testing.T) {
	got, err := tenant.ParseID(" Engineering ")
	if err != nil {
		t.Fatalf("ParseID: %v", err)
	}
	if got != tenant.ID("engineering") {
		t.Fatalf("ParseID = %q, want engineering", got)
	}

	for _, raw := range []string{"", "-tenant", "tenant/", "tenant space"} {
		if _, err := tenant.ParseID(raw); !errors.Is(err, tenant.ErrInvalid) {
			t.Errorf("ParseID(%q) error = %v, want ErrInvalid", raw, err)
		}
	}
}

func TestRequireRejectsMissingOrMalformedTenant(t *testing.T) {
	if _, err := tenant.Require(context.Background()); !errors.Is(err, tenant.ErrRequired) {
		t.Fatalf("Require(background) error = %v, want ErrRequired", err)
	}

	ctx := tenant.WithInfo(context.Background(), tenant.Info{
		ID:               "engineering",
		CanonicalBaseURL: "not-a-url",
	})
	if _, err := tenant.Require(ctx); !errors.Is(err, tenant.ErrInvalid) {
		t.Fatalf("Require(malformed) error = %v, want ErrInvalid", err)
	}
}

func TestTenantInfoIsCopiedAndUsesCanonicalPolicy(t *testing.T) {
	info := tenant.Info{
		ID:                         "engineering",
		CanonicalBaseURL:           "https://git.ags.ai/",
		CORSAllowedOrigins:         []string{"https://console.ags.ai"},
		ConnectedLoginEnabled:      true,
		OAuthDeviceVerificationURL: "https://console.ags.ai/device",
	}
	ctx := tenant.WithInfo(context.Background(), info)
	info.CORSAllowedOrigins[0] = "https://mutated.example"

	got, err := tenant.Require(ctx)
	if err != nil {
		t.Fatalf("Require: %v", err)
	}
	if got.ID != "engineering" || got.BaseURL() != "https://git.ags.ai" {
		t.Fatalf("tenant = %#v", got)
	}
	if got.Hostname() != "git.ags.ai" || !got.Secure() {
		t.Fatalf("canonical policy hostname=%q secure=%v", got.Hostname(), got.Secure())
	}
	if !got.AllowsOrigin("https://console.ags.ai") || got.AllowsOrigin("https://mutated.example") {
		t.Fatalf("CORS policy was not copied: %#v", got.CORSAllowedOrigins)
	}

	got.CORSAllowedOrigins[0] = "https://second-mutation.example"
	again, err := tenant.Require(ctx)
	if err != nil {
		t.Fatalf("Require second read: %v", err)
	}
	if !again.AllowsOrigin("https://console.ags.ai") {
		t.Fatal("context tenant policy was mutated by caller")
	}
}

func TestCopyPreservesTenantOnDetachedContext(t *testing.T) {
	requestCtx := tenant.WithInfo(context.Background(), tenant.Info{
		ID:               "clawmem",
		CanonicalBaseURL: "https://git.clawmem.ai",
	})
	detached, err := tenant.Copy(context.Background(), requestCtx)
	if err != nil {
		t.Fatalf("Copy: %v", err)
	}
	got, err := tenant.Require(detached)
	if err != nil {
		t.Fatalf("Require copied context: %v", err)
	}
	if got.ID != "clawmem" {
		t.Fatalf("copied tenant = %q, want clawmem", got.ID)
	}

	if _, err := tenant.Copy(context.Background(), context.Background()); !errors.Is(err, tenant.ErrRequired) {
		t.Fatalf("Copy missing tenant error = %v, want ErrRequired", err)
	}
}

func TestWithInfoRejectsTenantRebinding(t *testing.T) {
	tenantA := tenant.Info{
		ID:               "tenant-a",
		CanonicalBaseURL: "https://a.example.test",
	}
	tenantB := tenant.Info{
		ID:               "tenant-b",
		CanonicalBaseURL: "https://b.example.test",
	}
	ctx := tenant.WithInfo(context.Background(), tenantA)

	if _, err := tenant.Require(tenant.WithInfo(ctx, tenantB)); !errors.Is(err, tenant.ErrRebinding) {
		t.Fatalf("rebound tenant error = %v, want %v", err, tenant.ErrRebinding)
	}
	if got, err := tenant.Require(tenant.WithInfo(ctx, tenantA)); err != nil || !got.Equal(tenantA) {
		t.Fatalf("idempotent tenant binding = %#v, err %v", got, err)
	}
	if _, err := tenant.Copy(tenant.WithInfo(context.Background(), tenantB), ctx); !errors.Is(err, tenant.ErrRebinding) {
		t.Fatalf("copy over another tenant error = %v, want %v", err, tenant.ErrRebinding)
	}
}
