package main

import (
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"testing"

	"github.com/spf13/pflag"

	"iop/apps/control-plane/internal/credentialstore"

	_ "modernc.org/sqlite"
)

// newBootstrapTestStore opens a fresh SQLite store for bootstrap tests,
// returning the file path so the command can load config pointing at it.
func newBootstrapTestStore(t *testing.T) (string, *credentialstore.Store) {
	t.Helper()
	ctx := context.Background()
	tmpDir := t.TempDir()
	dbFile := filepath.Join(tmpDir, "bootstrap.db")
	store, err := credentialstore.Open(ctx, dbFile)
	if err != nil {
		t.Fatalf("open test store: %v", err)
	}
	t.Cleanup(func() { _ = store.Close() })
	return dbFile, store
}

func TestPrincipalBootstrapEmitsTokenOnce(t *testing.T) {
	dbFile, _ := newBootstrapTestStore(t)
	cfgPath := writeBootstrapConfig(t, dbFile)

	var stdout, stderr bytes.Buffer
	cmd := rootCmd()
	cmd.SetOut(&stdout)
	cmd.SetErr(&stderr)
	cmd.SetArgs([]string{"--config", cfgPath, "principal", "bootstrap", "--alias", "edge-prod"})

	if err := cmd.Execute(); err != nil {
		t.Fatalf("bootstrap: %v\nstderr: %s", err, stderr.String())
	}

	// Find the raw token in stdout (might be preceded by whitespace or other output).
	var rawToken string
	for _, line := range strings.Split(strings.TrimSpace(stdout.String()), "\n") {
		line = strings.TrimSpace(line)
		if len(line) == 64 {
			if _, err := hex.DecodeString(line); err == nil {
				rawToken = line
				break
			}
		}
	}
	if rawToken == "" {
		t.Fatalf("expected raw token (64 hex chars) on stdout, got: %q", stdout.String())
	}
	// stderr must not contain the raw token.
	if strings.Contains(stderr.String(), rawToken) {
		t.Fatalf("raw token leaked to stderr: %s", stderr.String())
	}
	// stdout must contain the token exactly once.
	if strings.Count(stdout.String(), rawToken) != 1 {
		t.Fatalf("raw token emitted %d times on stdout; expected 1", strings.Count(stdout.String(), rawToken))
	}
}

func TestPrincipalBootstrapRefusesExistingPrincipal(t *testing.T) {
	dbFile, store := newBootstrapTestStore(t)
	// Pre-create a principal so the bootstrap command must refuse.
	_, err := store.CreatePrincipalWithToken(context.Background(), credentialstore.CreatePrincipalInput{Alias: "existing"})
	if err != nil {
		t.Fatalf("create existing principal: %v", err)
	}
	cfgPath := writeBootstrapConfig(t, dbFile)

	var stdout, stderr bytes.Buffer
	cmd := rootCmd()
	cmd.SetOut(&stdout)
	cmd.SetErr(&stderr)
	cmd.SetArgs([]string{"--config", cfgPath, "principal", "bootstrap", "--alias", "edge-new"})

	err = cmd.Execute()
	if err == nil {
		t.Fatal("expected bootstrap to refuse when principal already exists")
	}
	if !strings.Contains(err.Error(), "already exists") {
		t.Fatalf("expected 'already exists' error, got: %v", err)
	}
	// stdout must not contain a raw token (64 hex chars).
	for _, line := range strings.Split(strings.TrimSpace(stdout.String()), "\n") {
		line = strings.TrimSpace(line)
		if len(line) == 64 {
			if _, err := hex.DecodeString(line); err == nil {
				t.Fatalf("stdout must not contain raw token when bootstrap refuses: %q", line)
			}
		}
	}
}

func TestPrincipalBootstrapPersistsAcrossReopen(t *testing.T) {
	dbFile, _ := newBootstrapTestStore(t)
	cfgPath := writeBootstrapConfig(t, dbFile)

	var stdout bytes.Buffer
	cmd := rootCmd()
	cmd.SetOut(&stdout)
	cmd.SetErr(os.Stderr)
	cmd.SetArgs([]string{"--config", cfgPath, "principal", "bootstrap", "--alias", "persist"})

	if err := cmd.Execute(); err != nil {
		t.Fatalf("bootstrap: %v", err)
	}
	rawToken := strings.TrimSpace(stdout.String())

	// Reopen the store and verify the principal persists.
	store2, err := credentialstore.Open(context.Background(), dbFile)
	if err != nil {
		t.Fatalf("reopen store: %v", err)
	}
	defer store2.Close()

	principals, err := store2.ListPrincipals(context.Background())
	if err != nil {
		t.Fatalf("list principals after reopen: %v", err)
	}
	if len(principals) != 1 {
		t.Fatalf("principal count after reopen: got %d want 1", len(principals))
	}
	if principals[0].Principal.Alias != "persist" {
		t.Fatalf("alias after reopen: got %q want %q", principals[0].Principal.Alias, "persist")
	}

	// Verify the raw token can authenticate via digest lookup.
	sum := sha256.Sum256([]byte(rawToken))
	digest := hex.EncodeToString(sum[:])
	_, token, err := store2.LookupTokenByDigest(context.Background(), digest)
	if err != nil {
		t.Fatalf("lookup by digest after reopen: %v", err)
	}
	if token == nil || token.Status != credentialstore.StatusActive {
		t.Fatalf("token must persist as active after reopen: %+v", token)
	}
}

func TestBootstrapLogsNeverContainToken(t *testing.T) {
	dbFile, _ := newBootstrapTestStore(t)
	cfgPath := writeBootstrapConfig(t, dbFile)

	// The bootstrap command writes the raw token directly to stdout via fmt.Fprintln.
	// It does not use zap for bootstrap logging; verify the token is isolated from
	// any logging path by checking stdout and stderr independently.
	var stdout, stderr bytes.Buffer
	cmd := rootCmd()
	cmd.SetOut(&stdout)
	cmd.SetErr(&stderr)
	cmd.SetArgs([]string{"--config", cfgPath, "principal", "bootstrap", "--alias", "log-test"})

	if err := cmd.Execute(); err != nil {
		t.Fatalf("bootstrap: %v", err)
	}

	rawToken := strings.TrimSpace(stdout.String())

	// The command does not use zap for bootstrap logging; it writes directly
	// to stdout. Verify the raw token does not appear in stdout beyond the
	// single emit line, and does not appear in stderr at all.
	stdoutContent := stdout.String()
	if strings.Count(stdoutContent, rawToken) != 1 {
		t.Fatalf("raw token appeared %d times in stdout; expected exactly 1", strings.Count(stdoutContent, rawToken))
	}
	if strings.Contains(stderr.String(), rawToken) {
		t.Fatalf("raw token leaked to stderr: %q", stderr.String())
	}

	// Verify the raw token does not appear in any DB column.
	store, err := credentialstore.Open(context.Background(), dbFile)
	if err != nil {
		t.Fatalf("open store for verification: %v", err)
	}
	defer store.Close()

	var allText string
	rows, err := store.DB().QueryContext(context.Background(), `SELECT * FROM principals`)
	if err != nil {
		t.Fatalf("scan principals: %v", err)
	}
	defer rows.Close()
	cols, _ := rows.Columns()
	for rows.Next() {
		vals := make([]interface{}, len(cols))
		ptrs := make([]interface{}, len(cols))
		for i := range vals {
			ptrs[i] = &vals[i]
		}
		if err := rows.Scan(ptrs...); err != nil {
			t.Fatalf("scan principal row: %v", err)
		}
		for _, v := range vals {
			if s, ok := v.(string); ok {
				allText += s
			}
		}
	}
	rows2, err := store.DB().QueryContext(context.Background(), `SELECT * FROM tokens`)
	if err != nil {
		t.Fatalf("scan tokens: %v", err)
	}
	defer rows2.Close()
	cols2, _ := rows2.Columns()
	for rows2.Next() {
		vals := make([]interface{}, len(cols2))
		ptrs := make([]interface{}, len(cols2))
		for i := range vals {
			ptrs[i] = &vals[i]
		}
		if err := rows2.Scan(ptrs...); err != nil {
			t.Fatalf("scan token row: %v", err)
		}
		for _, v := range vals {
			if s, ok := v.(string); ok {
				allText += s
			}
		}
	}
	if strings.Contains(allText, rawToken) {
		t.Fatal("raw token found in database columns")
	}

	// Verify digest is stored (not raw token).
	var digest string
	store.DB().QueryRowContext(context.Background(), `SELECT digest FROM tokens`).Scan(&digest)
	if digest == rawToken {
		t.Fatal("digest must not equal raw token")
	}
	expectedDigest := sha256Hex([]byte(rawToken))
	if digest != expectedDigest {
		t.Fatalf("stored digest mismatch: got %q want %q", digest, expectedDigest)
	}
}

func TestBootstrapRequiresAlias(t *testing.T) {
	dbFile, _ := newBootstrapTestStore(t)
	cfgPath := writeBootstrapConfig(t, dbFile)

	var stderr bytes.Buffer
	cmd := rootCmd()
	cmd.SetOut(os.Stderr)
	cmd.SetErr(&stderr)
	cmd.SetArgs([]string{"--config", cfgPath, "principal", "bootstrap"})

	err := cmd.Execute()
	if err == nil {
		t.Fatal("expected error when --alias is missing")
	}
	if !strings.Contains(err.Error(), "alias is required") {
		t.Fatalf("expected 'alias is required' error, got: %v", err)
	}
}

func TestBootstrapRejectsUnconfiguredDatabase(t *testing.T) {
	tmpDir := t.TempDir()
	cfgPath := filepath.Join(tmpDir, "no-db.yaml")
	if err := os.WriteFile(cfgPath, []byte("database:\n  url: \"\"\n"), 0o600); err != nil {
		t.Fatalf("write config: %v", err)
	}

	var stderr bytes.Buffer
	cmd := rootCmd()
	cmd.SetOut(os.Stderr)
	cmd.SetErr(&stderr)
	cmd.SetArgs([]string{"--config", cfgPath, "principal", "bootstrap", "--alias", "edge"})

	err := cmd.Execute()
	if err == nil {
		t.Fatal("expected error when database.url is empty")
	}
	if !strings.Contains(err.Error(), "database.url is required") {
		t.Fatalf("expected 'database.url is required' error, got: %v", err)
	}
}

type failingWriter struct {
	err error
}

func (w *failingWriter) Write(p []byte) (n int, err error) {
	return 0, w.err
}

func TestPrincipalBootstrapReturnsStdoutWriteError(t *testing.T) {
	dbFile, _ := newBootstrapTestStore(t)
	cfgPath := writeBootstrapConfig(t, dbFile)

	sentinelErr := errors.New("simulated write failure")
	fw := &failingWriter{err: sentinelErr}

	var stderr bytes.Buffer
	cmd := rootCmd()
	cmd.SetOut(fw)
	cmd.SetErr(&stderr)
	cmd.SetArgs([]string{"--config", cfgPath, "principal", "bootstrap", "--alias", "write-fail"})

	err := cmd.Execute()
	if err == nil {
		t.Fatal("expected error when stdout write fails")
	}
	if !errors.Is(err, sentinelErr) {
		t.Fatalf("expected error wrapping sentinel, got: %v", err)
	}

	errStr := err.Error() + "\n" + stderr.String()
	for _, word := range strings.Fields(errStr) {
		if len(word) == 64 {
			if _, hexErr := hex.DecodeString(word); hexErr == nil {
				t.Fatalf("raw token leaked in error or stderr: %q", word)
			}
		}
	}
}

func TestBootstrapCommandIsCLIOnlyRegistered(t *testing.T) {
	root := rootCmd()

	// Verify principal is a registered subcommand.
	var found bool
	for _, cmd := range root.Commands() {
		if cmd.Name() == "principal" {
			found = true
			break
		}
	}
	if !found {
		t.Fatal("principal command must be registered on root")
	}

	// Verify bootstrap is a subcommand of principal.
	principalCmd, _, err := root.Find([]string{"principal"})
	if err != nil {
		t.Fatalf("find principal: %v", err)
	}
	var bootstrapFound bool
	for _, cmd := range principalCmd.Commands() {
		if cmd.Name() == "bootstrap" {
			bootstrapFound = true
			break
		}
	}
	if !bootstrapFound {
		t.Fatal("bootstrap must be a subcommand of principal")
	}

	// Verify serve is still registered.
	var serveFound bool
	for _, cmd := range root.Commands() {
		if cmd.Name() == "serve" {
			serveFound = true
			break
		}
	}
	if !serveFound {
		t.Fatal("serve command must still be registered on root")
	}
}

func TestBootstrapCommandRemainsCLIOnly(t *testing.T) {
	// Structural proof: the bootstrap command exists only as a Cobra command.
	// No credential-bearing operation is added to the wire layer.
	root := rootCmd()

	// Find the bootstrap command.
	principalCmd, _, err := root.Find([]string{"principal"})
	if err != nil {
		t.Fatalf("find principal: %v", err)
	}
	bootstrapCmd, _, err := principalCmd.Find([]string{"bootstrap"})
	if err != nil {
		t.Fatalf("find bootstrap: %v", err)
	}

	// bootstrap must have --alias flag but no wire-related flags.
	var hasAlias, hasWireFlag bool
	bootstrapCmd.Flags().VisitAll(func(f *pflag.Flag) {
		if f.Name == "alias" {
			hasAlias = true
		}
		if f.Name == "wire" || f.Name == "listen" || f.Name == "server" {
			hasWireFlag = true
		}
	})
	if !hasAlias {
		t.Fatal("bootstrap must have --alias flag")
	}
	if hasWireFlag {
		t.Fatal("bootstrap must not have wire-related flags")
	}

	// Verify the bootstrap command has no Run (it uses RunE) and no PersistentPreRun
	// that could connect to a wire server.
	if bootstrapCmd.Run != nil {
		t.Fatal("bootstrap must use RunE, not Run")
	}
}

func TestBootstrapManagementIsAbsentFromWireSources(t *testing.T) {
	_, filename, _, ok := runtime.Caller(0)
	if !ok {
		t.Fatal("runtime.Caller failed")
	}
	repoRoot := filepath.Clean(filepath.Join(filepath.Dir(filename), "..", "..", "..", ".."))

	for _, source := range bootstrapWireSources(repoRoot) {
		content, err := os.ReadFile(source.path)
		if err != nil {
			t.Fatalf("read wire source %s: %v", source.path, err)
		}
		if violation := source.check(string(content)); violation != "" {
			t.Fatalf("wire source %s: %s", source.path, violation)
		}
	}
}

type bootstrapWireSource struct {
	path  string
	check func(string) string
}

func bootstrapWireSources(repoRoot string) []bootstrapWireSource {
	return []bootstrapWireSource{
		{path: filepath.Join(repoRoot, "proto/iop/control.proto"), check: protoBootstrapManagementViolation},
		{path: filepath.Join(repoRoot, "apps/control-plane/internal/wire/client.go"), check: clientServerBootstrapManagementViolation},
		{path: filepath.Join(repoRoot, "apps/client/lib/iop_wire/client_wire_client.dart"), check: dartBootstrapManagementViolation},
		{path: filepath.Join(repoRoot, "apps/client/lib/iop_wire/parser_map.dart"), check: dartBootstrapManagementViolation},
	}
}

var protoMessageDeclarationPattern = regexp.MustCompile(`(?m)^\s*message\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{`)

var allowedPrincipalProjectionMessages = map[string]struct{}{
	"ProjectedPrincipalToken":          {},
	"ProjectedPrincipalRoute":          {},
	"PrincipalProjection":              {},
	"PrincipalProjectionApplyRequest":  {},
	"PrincipalProjectionApplyResponse": {},
}

func protoBootstrapManagementViolation(content string) string {
	for _, match := range protoMessageDeclarationPattern.FindAllStringSubmatch(content, -1) {
		name := match[1]
		lower := strings.ToLower(name)
		if !strings.Contains(lower, "bootstrap") && !strings.Contains(lower, "credential") && !strings.Contains(lower, "principal") {
			continue
		}
		if _, allowed := allowedPrincipalProjectionMessages[name]; allowed {
			continue
		}
		return "forbidden management proto message " + name
	}
	return ""
}

func clientServerBootstrapManagementViolation(content string) string {
	if strings.Contains(strings.ToLower(content), "credentialstore") {
		return "must not import credentialstore"
	}
	return bootstrapManagementSymbolViolation(content)
}

func dartBootstrapManagementViolation(content string) string {
	return bootstrapManagementSymbolViolation(content)
}

func bootstrapManagementSymbolViolation(content string) string {
	lower := strings.ToLower(content)
	for _, symbol := range []string{"bootstrap", "credential", "principal"} {
		if strings.Contains(lower, symbol) {
			return "forbidden management symbol " + symbol
		}
	}
	return ""
}

func TestBootstrapManagementGuardRejectsForbiddenVariants(t *testing.T) {
	for _, tc := range []struct {
		name          string
		check         func(string) string
		content       string
		wantViolation bool
	}{
		{"proto principal create request", protoBootstrapManagementViolation, "message PrincipalCreateRequest {}", true},
		{"proto credential request", protoBootstrapManagementViolation, "message CredentialRequest {}", true},
		{"client lower camel create principal", clientServerBootstrapManagementViolation, "func createPrincipal() {}", true},
		{"Dart parser principal create request", dartBootstrapManagementViolation, "'iop.PrincipalCreateRequest': PrincipalCreateRequest.fromBuffer,", true},
		{"reserved projection foundation", protoBootstrapManagementViolation, "message ProjectedPrincipalToken {}\nmessage ProjectedPrincipalRoute {}\nmessage PrincipalProjection {}\nmessage PrincipalProjectionApplyRequest {}\nmessage PrincipalProjectionApplyResponse {}", false},
		{"Client hello proto", protoBootstrapManagementViolation, "message ClientHelloRequest {}\nmessage ClientHelloResponse {}", false},
		{"Client hello server", clientServerBootstrapManagementViolation, "func hello(req *iop.ClientHelloRequest) {}", false},
		{"Client hello Dart", dartBootstrapManagementViolation, "Future<ClientHelloResponse> hello() async => ClientHelloResponse();", false},
	} {
		t.Run(tc.name, func(t *testing.T) {
			violation := tc.check(tc.content)
			if tc.wantViolation && violation == "" {
				t.Fatalf("expected guard to reject %q", tc.content)
			}
			if !tc.wantViolation && violation != "" {
				t.Fatalf("expected guard to allow %q, got %s", tc.content, violation)
			}
		})
	}
}

// helper: writeBootstrapConfig writes a minimal config YAML pointing at the given database URL.
func writeBootstrapConfig(t *testing.T, dbFile string) string {
	t.Helper()
	path := filepath.Join(t.TempDir(), "bootstrap-config.yaml")
	body := "database:\n  url: \"" + dbFile + "\"\n"
	if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
		t.Fatalf("write config: %v", err)
	}
	return path
}

// sha256Hex returns the hex-encoded SHA-256 digest of data.
func sha256Hex(data []byte) string {
	sum := sha256.Sum256(data)
	return hex.EncodeToString(sum[:])
}
