package permissions

import (
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"strings"
	"testing"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
)

func TestEffectivePermissionsEmitsNewCorrelatedPhaseEvidenceWithoutIdentifiers(t *testing.T) {
	repo := &contractAdminRepo{
		effectiveSystemPerms: map[string]bool{string(constants.PermViewSetup): true},
		effectiveCustomPerms: map[string]bool{"CanExport": true},
		effectiveObjectPerms: []models.ObjectPermissions{{SobjectType: "SensitiveObject", PermissionsRead: true}},
		effectiveFieldPerms:  []models.FieldPermissions{{SobjectType: "SensitiveObject", Field: "Secret__c", PermissionsRead: true}},
		userProfileID:        "sensitive-profile",
	}
	metadata := &flsMetadataStub{schemas: []*models.ObjectMetadata{{
		APIName: "SensitiveObject", Fields: []models.FieldMetadata{{APIName: "Secret__c"}},
	}}}
	admin := NewContractPermissionAdmin(NewPermissionService(repo, metadata, nil), nil)
	ctx := context.WithValue(context.Background(), constants.ContextKeyRequestID, "raw-request-id")

	entries := capturePermissionReadEntries(t, func() {
		if _, err := admin.EffectivePermissions(ctx, &models.UserSession{ID: "sensitive-viewer", ProfileID: "viewer-profile"}, "sensitive-target"); err != nil {
			t.Fatalf("EffectivePermissions() error = %v, want nil", err)
		}
	})

	wantPhases := []string{
		"total:start",
		"target_authorization:start", "target_authorization:complete",
		"target_profile:start", "target_profile:complete",
		"system_permissions:start", "system_permissions:complete",
		"record_type_permissions:start", "record_type_permissions:complete",
		"custom_permissions:start", "custom_permissions:complete",
		"object_permissions:start", "object_permissions:complete",
		"field_permissions:start", "field_permissions:complete",
		"application_permissions:start", "application_permissions:complete",
		"navigation_permissions:start", "navigation_permissions:complete",
		"projection:start", "projection:complete",
		"total:complete",
	}
	if len(entries) != len(wantPhases) {
		t.Fatalf("phase entry count = %d, want %d: %+v", len(entries), len(wantPhases), entries)
	}
	for i, want := range wantPhases {
		got := entries[i]["phase"].(string) + ":" + entries[i]["state"].(string)
		if got != want {
			t.Fatalf("phase entry[%d] = %q, want %q", i, got, want)
		}
		if entries[i]["correlation"] != permissionReadCorrelation(ctx) {
			t.Fatalf("correlation = %v, want stable request hash", entries[i]["correlation"])
		}
		if entries[i]["target_scope"] != "other" {
			t.Fatalf("target_scope = %v, want other", entries[i]["target_scope"])
		}
	}
	if len(permissionReadCorrelation(ctx)) != 16 {
		t.Fatalf("correlation length = %d, want 16", len(permissionReadCorrelation(ctx)))
	}
	for _, entry := range entries {
		if entry["state"] == "complete" && entry["outcome"] != "ok" {
			t.Fatalf("completed phase outcome = %v, want ok: %+v", entry["outcome"], entry)
		}
	}
	if got := findPermissionReadEntry(t, entries, "field_permissions", "complete")["rows"]; got != float64(1) {
		t.Fatalf("field permission rows = %v, want 1", got)
	}
	if got := findPermissionReadEntry(t, entries, "projection", "complete")["rows"]; got != float64(2) {
		t.Fatalf("projection rows = %v, want 2", got)
	}
	if got := findPermissionReadEntry(t, entries, "total", "complete")["rows"]; got != float64(4) {
		t.Fatalf("total rows = %v, want 4", got)
	}

	encoded, err := json.Marshal(entries)
	if err != nil {
		t.Fatalf("marshal entries: %v", err)
	}
	for _, forbidden := range []string{
		"raw-request-id", "sensitive-viewer", "sensitive-target", "sensitive-profile",
		"viewer-profile", "SensitiveObject", "Secret__c", "CanExport", "SELECT",
	} {
		if strings.Contains(string(encoded), forbidden) {
			t.Fatalf("permission phase evidence contains forbidden value %q: %s", forbidden, encoded)
		}
	}
}

func TestEffectivePermissionsPhaseEvidenceClassifiesSanitizedFailure(t *testing.T) {
	repo := &contractAdminRepo{
		effectiveFieldErr: appErrors.NewInternalError(
			"failed to query effective field permissions",
			errors.New("driver: SELECT Secret__c FROM FieldPermissions"),
		),
	}
	metadata := &flsMetadataStub{schemas: []*models.ObjectMetadata{{
		APIName: "SensitiveObject", Fields: []models.FieldMetadata{{APIName: "Secret__c"}},
	}}}
	admin := NewContractPermissionAdmin(NewPermissionService(repo, metadata, nil), nil)

	entries := capturePermissionReadEntries(t, func() {
		_, _ = admin.EffectivePermissions(context.Background(), &models.UserSession{ID: "viewer", ProfileID: "profile"}, "")
	})

	fieldComplete := findPermissionReadEntry(t, entries, "field_permissions", "complete")
	if fieldComplete["outcome"] != "error" {
		t.Fatalf("field outcome = %v, want error", fieldComplete["outcome"])
	}
	totalComplete := findPermissionReadEntry(t, entries, "total", "complete")
	if totalComplete["outcome"] != "error" {
		t.Fatalf("total outcome = %v, want error", totalComplete["outcome"])
	}
	encoded, _ := json.Marshal(entries)
	for _, forbidden := range []string{"Secret__c", "FieldPermissions", "SELECT", "driver:"} {
		if strings.Contains(string(encoded), forbidden) {
			t.Fatalf("failure evidence contains raw error detail %q: %s", forbidden, encoded)
		}
	}
}

func TestPermissionReadOutcomeClassifiesContextTermination(t *testing.T) {
	if got := permissionReadOutcome(context.Background(), context.DeadlineExceeded); got != "deadline" {
		t.Fatalf("deadline outcome = %q, want deadline", got)
	}
	if got := permissionReadOutcome(context.Background(), context.Canceled); got != "canceled" {
		t.Fatalf("canceled outcome = %q, want canceled", got)
	}
}

type panickingContractAdminRepo struct {
	contractAdminRepo
}

func (p *panickingContractAdminRepo) GetEffectiveCustomPermissions(context.Context, string, string) (map[string]bool, error) {
	panic("raw provider panic")
}

func TestEffectivePermissionsPhaseEvidenceClosesPanickedPhaseAndTotal(t *testing.T) {
	admin := NewContractPermissionAdmin(NewPermissionService(&panickingContractAdminRepo{}, nil, nil), nil)
	var recovered any

	entries := capturePermissionReadEntries(t, func() {
		func() {
			defer func() { recovered = recover() }()
			_, _ = admin.EffectivePermissions(context.Background(), &models.UserSession{ID: "viewer", ProfileID: "profile"}, "")
		}()
	})

	if recovered != "raw provider panic" {
		t.Fatalf("recovered panic = %v, want original panic value", recovered)
	}
	customComplete := findPermissionReadEntry(t, entries, "custom_permissions", "complete")
	if customComplete["outcome"] != "panic" {
		t.Fatalf("custom phase outcome = %v, want panic", customComplete["outcome"])
	}
	totalComplete := findPermissionReadEntry(t, entries, "total", "complete")
	if totalComplete["outcome"] != "panic" {
		t.Fatalf("total phase outcome = %v, want panic", totalComplete["outcome"])
	}
	encoded, _ := json.Marshal(entries)
	if strings.Contains(string(encoded), "raw provider panic") {
		t.Fatalf("panic evidence contains raw panic value: %s", encoded)
	}
}

func capturePermissionReadEntries(t *testing.T, run func()) []map[string]any {
	t.Helper()
	var buf bytes.Buffer
	previous := log.Logger
	log.Logger = zerolog.New(&buf)
	t.Cleanup(func() {
		log.Logger = previous
	})

	run()

	entries := make([]map[string]any, 0)
	scanner := bufio.NewScanner(&buf)
	for scanner.Scan() {
		var entry map[string]any
		if err := json.Unmarshal(scanner.Bytes(), &entry); err != nil {
			t.Fatalf("decode log entry %q: %v", scanner.Text(), err)
		}
		if entry["event"] == "permission_read_phase" {
			entries = append(entries, entry)
		}
	}
	if err := scanner.Err(); err != nil {
		t.Fatalf("scan log entries: %v", err)
	}
	return entries
}

func findPermissionReadEntry(t *testing.T, entries []map[string]any, phase, state string) map[string]any {
	t.Helper()
	for _, entry := range entries {
		if entry["phase"] == phase && entry["state"] == state {
			return entry
		}
	}
	t.Fatalf("missing permission read phase %s/%s in %+v", phase, state, entries)
	return nil
}
