package metadata

import (
	"context"
	"database/sql"
	"errors"
	"testing"

	apperrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestFlowLifecycleMaintainsTriggerSubscriptionShadowState(t *testing.T) {
	ctx := context.Background()
	db, cleanup := openFlowLifecycleTestDB(t)
	defer cleanup()

	seedFlowDefinition(t, ctx, db, "300000000000001AAA", "301000000000001AAA", "301000000000002AAA")
	seedFlowVersion(t, ctx, db, "301000000000001AAA", "300000000000001AAA", 1, models.FlowStatusActive)
	seedFlowVersion(t, ctx, db, "301000000000002AAA", "300000000000001AAA", 2, models.FlowStatusDraft)

	service := newFlowLifecycleMetadataService(db)
	if err := service.ActivateFlowVersion(ctx, "301000000000002AAA"); err != nil {
		t.Fatalf("ActivateFlowVersion() error = %v", err)
	}

	rows := readLifecycleTriggerSubscriptions(t, ctx, db, "300000000000001AAA")
	if len(rows) != 2 {
		t.Fatalf("rows after activate = %d, want 2", len(rows))
	}
	for _, row := range rows {
		if !row.IsActive {
			t.Fatalf("row %+v IsActive=false after activate", row)
		}
		if row.FlowVersionId != "301000000000002AAA" {
			t.Fatalf("row FlowVersionId = %q, want activated version", row.FlowVersionId)
		}
	}

	if err := service.DeactivateFlowVersion(ctx, "301000000000002AAA"); err != nil {
		t.Fatalf("DeactivateFlowVersion() error = %v", err)
	}
	rows = readLifecycleTriggerSubscriptions(t, ctx, db, "300000000000001AAA")
	if len(rows) != 2 {
		t.Fatalf("rows after deactivate = %d, want 2 disabled rows", len(rows))
	}
	for _, row := range rows {
		if row.IsActive {
			t.Fatalf("row %+v IsActive=true after deactivate", row)
		}
	}

	if err := service.DeleteFlowDefinition(ctx, "300000000000001AAA"); err != nil {
		t.Fatalf("DeleteFlowDefinition() error = %v", err)
	}
	rows = readLifecycleTriggerSubscriptions(t, ctx, db, "300000000000001AAA")
	if len(rows) != 0 {
		t.Fatalf("rows after delete = %d, want 0", len(rows))
	}
}

func TestFlowLifecycleUpdateActiveVersionReconcilesTriggerSubscriptionShadowState(t *testing.T) {
	ctx := context.Background()
	db, cleanup := openFlowLifecycleTestDB(t)
	defer cleanup()

	seedLifecycleObject(t, ctx, db, "obj-other", "OtherObject", "b00")
	seedFlowDefinition(t, ctx, db, "300000000000001AAA", "301000000000001AAA", "301000000000001AAA")
	seedFlowVersion(t, ctx, db, "301000000000001AAA", "300000000000001AAA", 1, models.FlowStatusActive)

	service := newFlowLifecycleMetadataService(db)
	if err := service.ActivateFlowVersion(ctx, "301000000000001AAA"); err != nil {
		t.Fatalf("ActivateFlowVersion() error = %v", err)
	}

	update := &models.FlowVersion{
		DefinitionId: "300000000000001AAA",
		Status:       models.FlowStatusActive,
		ProcessType:  "AutoLaunchedFlow",
		Metadata: models.FlowMetadata{
			Start: &models.FlowStart{
				TriggerType:       "RecordBeforeSave",
				RecordTriggerType: "Create",
				Object:            "OtherObject",
			},
		},
	}
	if err := service.UpdateFlowVersion(ctx, "301000000000001AAA", update); err != nil {
		t.Fatalf("UpdateFlowVersion() error = %v", err)
	}

	rows := activeLifecycleTriggerSubscriptions(readLifecycleTriggerSubscriptions(t, ctx, db, "300000000000001AAA"))
	if len(rows) != 1 {
		t.Fatalf("active rows after active metadata update = %d, want 1", len(rows))
	}
	row := rows[0]
	if row.KeyPrefix != "b00" {
		t.Fatalf("KeyPrefix after active metadata update = %q, want b00", row.KeyPrefix)
	}
	if row.EventType != "RecordBeforeSave" {
		t.Fatalf("EventType after active metadata update = %q, want RecordBeforeSave", row.EventType)
	}
	if row.RecordTriggerType != "Create" {
		t.Fatalf("RecordTriggerType after active metadata update = %q, want Create", row.RecordTriggerType)
	}
	if !row.IsActive {
		t.Fatalf("row %+v inactive after active metadata update", row)
	}
}

func TestFlowDefinitionPatchRejectsShadowAuthorityMutations(t *testing.T) {
	ctx := context.Background()
	db, cleanup := openFlowLifecycleTestDB(t)
	defer cleanup()

	seedFlowDefinition(t, ctx, db, "300000000000001AAA", "301000000000001AAA", "301000000000001AAA")
	seedFlowVersion(t, ctx, db, "301000000000001AAA", "300000000000001AAA", 1, models.FlowStatusActive)

	service := newFlowLifecycleMetadataService(db)
	if err := service.ActivateFlowVersion(ctx, "301000000000001AAA"); err != nil {
		t.Fatalf("ActivateFlowVersion() error = %v", err)
	}

	patch := &models.FlowDefinitionPatch{
		TriggerType:     stringPtr("RecordBeforeSave"),
		ActiveVersionId: stringPtr("301000000000099AAA"),
	}
	err := service.UpdateFlowDefinition(ctx, "300000000000001AAA", patch)
	if err == nil {
		t.Fatal("UpdateFlowDefinition() error = nil, want FLS denied error for trigger authority patch")
	}
	var deniedErr *apperrors.FLSDeniedError
	if !errors.As(err, &deniedErr) {
		t.Fatalf("UpdateFlowDefinition() error = %T, want FLSDeniedError", err)
	}
	wantFields := []string{"ActiveVersionId", "TriggerType"}
	if len(deniedErr.Fields) != len(wantFields) {
		t.Fatalf("denied fields = %v, want %v", deniedErr.Fields, wantFields)
	}
	for i, want := range wantFields {
		if deniedErr.Fields[i] != want {
			t.Fatalf("denied fields[%d] = %q, want %q (full=%v)", i, deniedErr.Fields[i], want, deniedErr.Fields)
		}
	}

	def := readLifecycleDefinition(t, ctx, db, "300000000000001AAA")
	if def.TriggerType != "RecordAfterSave" {
		t.Fatalf("TriggerType after rejected patch = %q, want RecordAfterSave", def.TriggerType)
	}
	rows := activeLifecycleTriggerSubscriptions(readLifecycleTriggerSubscriptions(t, ctx, db, "300000000000001AAA"))
	if len(rows) != 2 {
		t.Fatalf("active rows after rejected patch = %d, want 2", len(rows))
	}
	for _, row := range rows {
		if row.EventType != "RecordAfterSave" {
			t.Fatalf("EventType after rejected patch = %q, want RecordAfterSave", row.EventType)
		}
		if !row.IsActive {
			t.Fatalf("row %+v inactive after rejected patch", row)
		}
	}
}

func TestFlowDefinitionCreateRejectsShadowAuthorityMutations(t *testing.T) {
	ctx := context.Background()
	db, cleanup := openFlowLifecycleTestDB(t)
	defer cleanup()

	service := newFlowLifecycleMetadataService(db)
	definition := &models.FlowDefinition{
		Id:                   "300000000000099AAA",
		DeveloperName:        "Probe_Create_Block",
		MasterLabel:          "Probe Create Block",
		ProcessType:          "AutoLaunchedFlow",
		TriggerType:          "RecordAfterSave",
		TriggerObjectOrEvent: "ProbeObject",
		ActiveVersionId:      "301000000000001AAA",
		LatestVersionId:      "301000000000001AAA",
	}
	err := service.CreateFlowDefinition(ctx, definition)
	if err == nil {
		t.Fatal("CreateFlowDefinition() error = nil, want FLS denied error for shadow-authority create")
	}
	var deniedErr *apperrors.FLSDeniedError
	if !errors.As(err, &deniedErr) {
		t.Fatalf("CreateFlowDefinition() error = %T, want FLSDeniedError", err)
	}
	wantFields := []string{
		"ActiveVersionId",
		"LatestVersionId",
		"ProcessType",
		"TriggerObjectOrEvent",
		"TriggerType",
	}
	if len(deniedErr.Fields) != len(wantFields) {
		t.Fatalf("denied fields = %v, want %v", deniedErr.Fields, wantFields)
	}
	for i, want := range wantFields {
		if deniedErr.Fields[i] != want {
			t.Fatalf("denied fields[%d] = %q, want %q (full=%v)", i, deniedErr.Fields[i], want, deniedErr.Fields)
		}
	}

	var count int
	if err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM FlowDefinition`).Scan(&count); err != nil {
		t.Fatalf("count FlowDefinition rows: %v", err)
	}
	if count != 0 {
		t.Fatalf("FlowDefinition rows after rejected create = %d, want 0", count)
	}
}

func TestFlowDefinitionLifecycleCreateAllowsProjectionAuthority(t *testing.T) {
	ctx := context.Background()
	db, cleanup := openFlowLifecycleTestDB(t)
	defer cleanup()

	service := newFlowLifecycleMetadataService(db)
	definition := &models.FlowDefinition{
		Id:                   "300000000000100AAA",
		DeveloperName:        "Lifecycle_Create",
		MasterLabel:          "Lifecycle Create",
		Description:          "created from flow version lifecycle",
		ProcessType:          "AutoLaunchedFlow",
		TriggerType:          "RecordAfterSave",
		TriggerObjectOrEvent: "ProbeObject",
	}
	if err := service.CreateFlowDefinitionForLifecycle(ctx, definition); err != nil {
		t.Fatalf("CreateFlowDefinitionForLifecycle() error = %v", err)
	}

	def := readLifecycleDefinition(t, ctx, db, "300000000000100AAA")
	if def.ProcessType != "AutoLaunchedFlow" {
		t.Fatalf("ProcessType = %q, want AutoLaunchedFlow", def.ProcessType)
	}
	if def.TriggerType != "RecordAfterSave" {
		t.Fatalf("TriggerType = %q, want RecordAfterSave", def.TriggerType)
	}
	if def.TriggerObjectOrEvent != "ProbeObject" {
		t.Fatalf("TriggerObjectOrEvent = %q, want ProbeObject", def.TriggerObjectOrEvent)
	}
	rows := readLifecycleTriggerSubscriptions(t, ctx, db, "300000000000100AAA")
	if len(rows) != 0 {
		t.Fatalf("trigger subscription rows after lifecycle definition create = %d, want 0", len(rows))
	}
}

type lifecycleTriggerSubscriptionRow struct {
	KeyPrefix         string
	EventType         string
	RecordTriggerType string
	FlowVersionId     string
	IsActive          bool
}

func seedLifecycleObject(t *testing.T, ctx context.Context, db *sql.DB, id string, apiName string, keyPrefix string) {
	t.Helper()
	if _, err := db.ExecContext(ctx, `INSERT INTO _System_Object (Id, ApiName, KeyPrefix, IsDeleted) VALUES (?, ?, ?, 0)`, id, apiName, keyPrefix); err != nil {
		t.Fatalf("seed _System_Object: %v", err)
	}
}

func readLifecycleTriggerSubscriptions(t *testing.T, ctx context.Context, db *sql.DB, definitionID string) []lifecycleTriggerSubscriptionRow {
	t.Helper()
	rows, err := db.QueryContext(ctx, `
		SELECT KeyPrefix, EventType, RecordTriggerType, FlowVersionId, IsActive
		FROM _System_TriggerSubscription
		WHERE FlowDefinitionId = ?
		ORDER BY EventType, RecordTriggerType`, definitionID)
	if err != nil {
		t.Fatalf("query trigger subscriptions: %v", err)
	}
	defer rows.Close()

	var got []lifecycleTriggerSubscriptionRow
	for rows.Next() {
		var row lifecycleTriggerSubscriptionRow
		if err := rows.Scan(&row.KeyPrefix, &row.EventType, &row.RecordTriggerType, &row.FlowVersionId, &row.IsActive); err != nil {
			t.Fatalf("scan trigger subscription row: %v", err)
		}
		got = append(got, row)
	}
	if err := rows.Err(); err != nil {
		t.Fatalf("iterate trigger subscriptions: %v", err)
	}
	return got
}

func activeLifecycleTriggerSubscriptions(rows []lifecycleTriggerSubscriptionRow) []lifecycleTriggerSubscriptionRow {
	var active []lifecycleTriggerSubscriptionRow
	for _, row := range rows {
		if row.IsActive {
			active = append(active, row)
		}
	}
	return active
}

func stringPtr(v string) *string {
	return &v
}
