package setupcmd

import (
	"bytes"
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"os"
	"path/filepath"
	"strings"
	"testing"

	mappingv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/layoutroutingidentitymappingv1"
	routingv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/layoutroutingsecurityv1"
	"github.com/pingcap-inc/nexuscrm-controlplane/catalogtenant"
)

func TestInspectRoutingIdentityMappingFromSourcesRequiresExactCoverage(t *testing.T) {
	proposal := testRoutingIdentityMappingProposal()
	target := &routingIdentityMappingTestTarget{target: catalogtenant.ObservedActiveOrgBinding{
		OrgID:             proposal.OrgID,
		TenantNamespaceID: "tenant.example",
	}}
	identities := routingIdentityMappingTestIdentityReader{
		profiles: []string{"profile-1"},
		roles:    []string{"role-1"},
	}

	actual, err := inspectRoutingIdentityMappingFromSources(context.Background(), target, identities, proposal)
	if err != nil {
		t.Fatalf("inspectRoutingIdentityMappingFromSources() error = %v", err)
	}
	wantDigest, err := mappingv1.ProposalDigest(proposal)
	if err != nil {
		t.Fatalf("ProposalDigest() error = %v", err)
	}
	if actual.Mode != "halted_read_only_routing_identity_mapping_preflight/v1" ||
		actual.ProposalDigest != wantDigest ||
		actual.OrgID != proposal.OrgID ||
		actual.TenantNamespaceID != "tenant.example" ||
		actual.ActiveProfileCount != 1 || actual.ActiveUserRoleCount != 1 {
		t.Fatalf("unexpected preflight: %+v", actual)
	}
	if target.orgID != proposal.OrgID {
		t.Fatalf("tenant target inspected OrgID = %q, want %q", target.orgID, proposal.OrgID)
	}
}

func TestInspectRoutingIdentityMappingFromSourcesAcceptsPresentEmptyDimensions(t *testing.T) {
	proposal := testRoutingIdentityMappingProposal()
	proposal.Profiles = []mappingv1.ProfileMapping{}
	proposal.Roles = []mappingv1.RoleMapping{}

	actual, err := inspectRoutingIdentityMappingFromSources(
		context.Background(),
		&routingIdentityMappingTestTarget{target: catalogtenant.ObservedActiveOrgBinding{
			OrgID:             proposal.OrgID,
			TenantNamespaceID: "tenant.example",
		}},
		routingIdentityMappingTestIdentityReader{profiles: []string{}, roles: []string{}},
		proposal,
	)
	if err != nil {
		t.Fatalf("inspectRoutingIdentityMappingFromSources() error = %v", err)
	}
	if actual.ActiveProfileCount != 0 || actual.ActiveUserRoleCount != 0 {
		t.Fatalf("unexpected empty-dimension preflight: %+v", actual)
	}
}

func TestInspectRoutingIdentityMappingFromSourcesRejectsMismatchAndIncompleteCoverage(t *testing.T) {
	proposal := testRoutingIdentityMappingProposal()
	tests := []struct {
		name       string
		target     catalogtenant.ObservedActiveOrgBinding
		identities routingIdentityMappingTestIdentityReader
		want       string
	}{
		{
			name: "target org differs",
			target: catalogtenant.ObservedActiveOrgBinding{
				OrgID:             "org-other",
				TenantNamespaceID: "tenant.example",
			},
			identities: routingIdentityMappingTestIdentityReader{profiles: []string{"profile-1"}, roles: []string{"role-1"}},
			want:       "tenant target differs",
		},
		{
			name:       "target namespace missing",
			target:     catalogtenant.ObservedActiveOrgBinding{OrgID: proposal.OrgID},
			identities: routingIdentityMappingTestIdentityReader{profiles: []string{"profile-1"}, roles: []string{"role-1"}},
			want:       "tenant target differs",
		},
		{
			name:   "missing active profile",
			target: catalogtenant.ObservedActiveOrgBinding{OrgID: proposal.OrgID, TenantNamespaceID: "tenant.example"},
			identities: routingIdentityMappingTestIdentityReader{
				profiles: nil,
				roles:    []string{"role-1"},
			},
			want: "Profile mapping does not have exact active coverage",
		},
		{
			name:   "mapping references inactive role",
			target: catalogtenant.ObservedActiveOrgBinding{OrgID: proposal.OrgID, TenantNamespaceID: "tenant.example"},
			identities: routingIdentityMappingTestIdentityReader{
				profiles: []string{"profile-1"},
				roles:    []string{"role-2"},
			},
			want: "UserRole mapping does not resolve an active row",
		},
		{
			name:   "duplicate active profile",
			target: catalogtenant.ObservedActiveOrgBinding{OrgID: proposal.OrgID, TenantNamespaceID: "tenant.example"},
			identities: routingIdentityMappingTestIdentityReader{
				profiles: []string{"profile-1", "profile-1"},
				roles:    []string{"role-1"},
			},
			want: "duplicate active Profile ID",
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			_, err := inspectRoutingIdentityMappingFromSources(
				context.Background(),
				&routingIdentityMappingTestTarget{target: test.target},
				test.identities,
				proposal,
			)
			if err == nil || !strings.Contains(err.Error(), test.want) {
				t.Fatalf("inspectRoutingIdentityMappingFromSources() error = %v, want %q", err, test.want)
			}
		})
	}
}

func TestInspectRoutingIdentityMappingFromSourcesAcceptsCompleteMappingsWithSystemAndAppNamespaces(t *testing.T) {
	proposal := testRoutingIdentityMappingProposal()
	proposal.Profiles[0].Key.NamespaceID = "io.nexuscrm.system"
	proposal.Roles[0].Key.NamespaceID = "app.example"

	preflight, err := inspectRoutingIdentityMappingFromSources(
		context.Background(),
		&routingIdentityMappingTestTarget{target: catalogtenant.ObservedActiveOrgBinding{
			OrgID:             proposal.OrgID,
			TenantNamespaceID: "tenant.example",
		}},
		routingIdentityMappingTestIdentityReader{profiles: []string{"profile-1"}, roles: []string{"role-1"}},
		proposal,
	)
	if err != nil {
		t.Fatalf("inspectRoutingIdentityMappingFromSources() error = %v", err)
	}
	if preflight.TenantNamespaceID != "tenant.example" {
		t.Fatalf("preflight TenantNamespaceID = %q, want tenant target", preflight.TenantNamespaceID)
	}
}

func TestValidateExactMappingDimensionRejectsDuplicateResolvedIDs(t *testing.T) {
	profileMappings := []mappingv1.ProfileMapping{
		{ResolvedProfileID: "profile-1"},
		{ResolvedProfileID: "profile-1"},
	}
	if err := validateExactMappingDimension(
		profileMappings,
		[]string{"profile-1", "profile-2"},
		func(mapping mappingv1.ProfileMapping) string { return mapping.ResolvedProfileID },
		"Profile",
	); err == nil || !strings.Contains(err.Error(), "duplicate Profile mapping resolved ID") {
		t.Fatalf("Profile duplicate error = %v", err)
	}

	roleMappings := []mappingv1.RoleMapping{
		{ResolvedRoleID: "role-1"},
		{ResolvedRoleID: "role-1"},
	}
	if err := validateExactMappingDimension(
		roleMappings,
		[]string{"role-1", "role-2"},
		func(mapping mappingv1.RoleMapping) string { return mapping.ResolvedRoleID },
		"UserRole",
	); err == nil || !strings.Contains(err.Error(), "duplicate UserRole mapping resolved ID") {
		t.Fatalf("UserRole duplicate error = %v", err)
	}
}

func TestRunRoutingIdentityMappingPreflightCommandUsesProposalOrgWithoutOrgFlag(t *testing.T) {
	proposal := testRoutingIdentityMappingProposal()
	raw, err := mappingv1.CanonicalProposalBytes(proposal)
	if err != nil {
		t.Fatalf("CanonicalProposalBytes() error = %v", err)
	}
	path := filepath.Join(t.TempDir(), "mapping.bin")
	if err := os.WriteFile(path, raw, 0o600); err != nil {
		t.Fatal(err)
	}

	var stdout, stderr bytes.Buffer
	called := false
	code := runRoutingIdentityMappingPreflightCommand(
		context.Background(),
		nil,
		[]string{"--proposal-file", path},
		&stdout,
		&stderr,
		func(_ context.Context, _ *sql.DB, actual mappingv1.Proposal) (routingIdentityMappingPreflight, error) {
			called = true
			if actual.OrgID != proposal.OrgID {
				t.Fatalf("proposal OrgID = %q, want %q", actual.OrgID, proposal.OrgID)
			}
			return routingIdentityMappingPreflight{Mode: "test"}, nil
		},
	)
	if code != 0 || !called || stderr.Len() != 0 {
		t.Fatalf("runRoutingIdentityMappingPreflightCommand() code=%d called=%t stderr=%q", code, called, stderr.String())
	}
	var output routingIdentityMappingPreflight
	if err := json.Unmarshal(stdout.Bytes(), &output); err != nil {
		t.Fatalf("decode output: %v", err)
	}
	if output.Mode != "test" {
		t.Fatalf("output = %+v", output)
	}

	stdout.Reset()
	stderr.Reset()
	code = runRoutingIdentityMappingPreflightCommand(
		context.Background(), nil, []string{"--proposal-file", path, "--org-id", proposal.OrgID}, &stdout, &stderr,
		func(context.Context, *sql.DB, mappingv1.Proposal) (routingIdentityMappingPreflight, error) {
			t.Fatal("unexpected inspection")
			return routingIdentityMappingPreflight{}, nil
		},
	)
	if code != 2 || stderr.String() != "inspect-routing-identity-mapping: invalid arguments\n" {
		t.Fatalf("unexpected free-org flag result: code=%d stderr=%q", code, stderr.String())
	}
}

func TestRunRoutingIdentityMappingPreflightCommandDoesNotEmitResolvedLocalIDs(t *testing.T) {
	proposal := testRoutingIdentityMappingProposal()
	raw, err := mappingv1.CanonicalProposalBytes(proposal)
	if err != nil {
		t.Fatalf("CanonicalProposalBytes() error = %v", err)
	}
	path := filepath.Join(t.TempDir(), "mapping.bin")
	if err := os.WriteFile(path, raw, 0o600); err != nil {
		t.Fatal(err)
	}

	var stdout, stderr bytes.Buffer
	code := runRoutingIdentityMappingPreflightCommand(
		context.Background(),
		nil,
		[]string{"--proposal-file", path},
		&stdout,
		&stderr,
		func(context.Context, *sql.DB, mappingv1.Proposal) (routingIdentityMappingPreflight, error) {
			return routingIdentityMappingPreflight{}, errors.New(`duplicate ResolvedProfileID "profile-1"`)
		},
	)
	if code != 1 || stdout.Len() != 0 || stderr.String() != "inspect-routing-identity-mapping: preflight failed\n" {
		t.Fatalf("unexpected failed-preflight output: code=%d stdout=%q stderr=%q", code, stdout.String(), stderr.String())
	}
	if strings.Contains(stderr.String(), "profile-1") {
		t.Fatalf("stderr leaked target-local ID: %q", stderr.String())
	}
}

func TestReadCanonicalRoutingIdentityMappingProposalRejectsNonRegularAndNoncanonicalInput(t *testing.T) {
	directory := t.TempDir()
	if _, err := readCanonicalRoutingIdentityMappingProposal(directory); err == nil || !strings.Contains(err.Error(), "regular") {
		t.Fatalf("directory error = %v, want regular-file rejection", err)
	}
	path := filepath.Join(directory, "not-canonical.bin")
	if err := os.WriteFile(path, []byte("not a mapping proposal"), 0o600); err != nil {
		t.Fatal(err)
	}
	if _, err := readCanonicalRoutingIdentityMappingProposal(path); err == nil {
		t.Fatal("noncanonical proposal accepted")
	}
}

type routingIdentityMappingTestTarget struct {
	target catalogtenant.ObservedActiveOrgBinding
	err    error
	orgID  string
}

func (target *routingIdentityMappingTestTarget) InspectActiveOrgBindingLineage(_ context.Context, orgID string) (catalogtenant.ObservedActiveOrgBinding, error) {
	target.orgID = orgID
	return target.target, target.err
}

type routingIdentityMappingTestIdentityReader struct {
	profiles    []string
	roles       []string
	profileErr  error
	userRoleErr error
}

func (reader routingIdentityMappingTestIdentityReader) ActiveProfileIDs(context.Context) ([]string, error) {
	return reader.profiles, reader.profileErr
}

func (reader routingIdentityMappingTestIdentityReader) ActiveUserRoleIDs(context.Context) ([]string, error) {
	return reader.roles, reader.userRoleErr
}

type routingIdentityMappingUnexpectedIdentityReader struct{}

func (routingIdentityMappingUnexpectedIdentityReader) ActiveProfileIDs(context.Context) ([]string, error) {
	return nil, errors.New("unexpected Profile identity read")
}

func (routingIdentityMappingUnexpectedIdentityReader) ActiveUserRoleIDs(context.Context) ([]string, error) {
	return nil, errors.New("unexpected UserRole identity read")
}

func testRoutingIdentityMappingProposal() mappingv1.Proposal {
	return mappingv1.Proposal{
		OrgID:                   "org-test",
		CanonicalizationVersion: mappingv1.CanonicalizationVersion,
		KeyGrammarVersion:       routingv1.KeyGrammarVersion,
		Profiles: []mappingv1.ProfileMapping{{
			Key:               routingv1.ProfileKey{NamespaceID: "tenant.example", LocalKey: "profile-one"},
			ResolvedProfileID: "profile-1",
		}},
		Roles: []mappingv1.RoleMapping{{
			Key:            routingv1.RoleKey{NamespaceID: "tenant.example", LocalKey: "role-one"},
			ResolvedRoleID: "role-1",
		}},
	}
}

var _ routingIdentityMappingTargetInspector = (*routingIdentityMappingTestTarget)(nil)
var _ routingIdentityMappingIdentityReader = routingIdentityMappingTestIdentityReader{}
var _ routingIdentityMappingIdentityReader = routingIdentityMappingUnexpectedIdentityReader{}
