package dialectsfcrm

import (
	"context"
	"strings"
	"testing"

	cp "github.com/pingcap-inc/nexuscrm-contract/pkg/controlplane"
)

func TestApprovalProcessManagedReconcileRetiresOnlyStaleDefinitions(t *testing.T) {
	store := &approvalProcessReconcileTestStore{
		approvalProcessApplyTestStore: approvalProcessApplyTestStore{rows: map[string][]cp.ComponentRow{
			approvalProcessDefinitionTable: {
				{"Id": "keep", "DeveloperName": "Keep", "TableEnumOrId": "Quote", "OwnerPack": "sales", "State": "Active", "IsActive": int64(1)},
				{"Id": "stale", "DeveloperName": "Stale", "TableEnumOrId": "Quote", "OwnerPack": "sales", "State": "Active", "IsActive": int64(1), "Metadata": "retained", "ProcessOrder": 7},
				{"Id": "mismatch", "DeveloperName": "Mismatch", "TableEnumOrId": "Quote", "OwnerPack": "sales", "State": "Inactive", "IsActive": int64(1)},
				{"Id": "retired", "DeveloperName": "Retired", "TableEnumOrId": "Quote", "OwnerPack": "sales", "State": "Inactive", "IsActive": int64(0)},
			},
		}},
		affected: 1,
	}
	request := cp.ComponentManagedSetRequest{PackName: "sales", Kind: cp.ComponentKindApprovalProcess}
	if err := reconcileApprovalProcessManagedSet(context.Background(), store, request, []string{"Quote/Keep"}); err != nil {
		t.Fatal(err)
	}
	if len(store.locks) != 1 || store.locks[0].Table != approvalProcessDefinitionTable || store.locks[0].Equal["OwnerPack"] != "sales" || len(store.locks[0].Equal) != 1 {
		t.Fatalf("locks = %#v", store.locks)
	}
	if len(store.updates) != 2 {
		t.Fatalf("updates = %#v", store.updates)
	}
	for index, id := range []string{"stale", "mismatch"} {
		update := store.updates[index]
		if update.Match["Id"] != id || update.Match["OwnerPack"] != "sales" || len(update.Match) != 2 || update.Values["State"] != approvalProcessState(false) || update.Values["IsActive"] != false || len(update.Values) != 2 || len(update.CurrentTimestampColumns) != 1 || update.CurrentTimestampColumns[0] != "LastModifiedDate" {
			t.Fatalf("update %d = %#v", index, update)
		}
	}
	if len(store.inserts) != 0 || len(store.deletes) != 0 {
		t.Fatalf("child/row mutations = inserts %#v deletes %#v", store.inserts, store.deletes)
	}
}

func TestApprovalProcessManagedReconcileDroppedToZeroAndExactAffectedRow(t *testing.T) {
	request := cp.ComponentManagedSetRequest{PackName: "sales", Kind: cp.ComponentKindApprovalProcess}
	for _, affected := range []int64{0, 1, 2} {
		store := &approvalProcessReconcileTestStore{
			approvalProcessApplyTestStore: approvalProcessApplyTestStore{rows: map[string][]cp.ComponentRow{
				approvalProcessDefinitionTable: {{"Id": "stale", "DeveloperName": "Stale", "TableEnumOrId": "Quote", "OwnerPack": "sales", "State": "Active", "IsActive": true}},
			}},
			affected: affected,
		}
		err := reconcileApprovalProcessManagedSet(context.Background(), store, request, nil)
		if affected == 1 && err != nil {
			t.Fatalf("dropped-to-zero reconcile: %v", err)
		}
		if affected != 1 && (err == nil || !strings.Contains(err.Error(), "expected exactly one")) {
			t.Fatalf("affected %d: error/updates = %v/%#v", affected, err, store.updates)
		}
		if len(store.updates) != 1 {
			t.Fatalf("affected %d: updates = %#v", affected, store.updates)
		}
	}
}

func TestApprovalProcessManagedReconcileValidatesWholeIdentitySetBeforeMutation(t *testing.T) {
	request := cp.ComponentManagedSetRequest{PackName: "sales", Kind: cp.ComponentKindApprovalProcess}
	for name, declared := range map[string][]string{
		"invalid":      {"Quote/not/canonical"},
		"duplicate":    {"Quote/Approval", "quote/approval"},
		"cross object": {"Quote/Approval", "Account/approval"},
	} {
		t.Run(name, func(t *testing.T) {
			store := &approvalProcessReconcileTestStore{}
			if err := reconcileApprovalProcessManagedSet(context.Background(), store, request, declared); err == nil || len(store.locks)+len(store.updates) != 0 {
				t.Fatalf("error/locks/updates = %v/%d/%d", err, len(store.locks), len(store.updates))
			}
		})
	}
	crossSet := &approvalProcessReconcileTestStore{
		approvalProcessApplyTestStore: approvalProcessApplyTestStore{rows: map[string][]cp.ComponentRow{
			approvalProcessDefinitionTable: {{"Id": "live", "DeveloperName": "approval", "TableEnumOrId": "Account", "OwnerPack": "sales", "State": "Active", "IsActive": true}},
		}}, affected: 1,
	}
	if err := reconcileApprovalProcessManagedSet(context.Background(), crossSet, request, []string{"Quote/Approval"}); err == nil || len(crossSet.updates) != 0 {
		t.Fatalf("declared/live cross-object error/updates = %v/%#v", err, crossSet.updates)
	}
	for name, rows := range map[string][]cp.ComponentRow{
		"escaped owner": {
			{"Id": "stale", "DeveloperName": "Stale", "TableEnumOrId": "Quote", "OwnerPack": "sales", "State": "Active", "IsActive": true},
			{"Id": "escaped", "DeveloperName": "Other", "TableEnumOrId": "Quote", "OwnerPack": "other", "State": "Active", "IsActive": true},
		},
		"duplicate": {
			{"Id": "one", "DeveloperName": "Approval", "TableEnumOrId": "Quote", "OwnerPack": "sales", "State": "Active", "IsActive": true},
			{"Id": "two", "DeveloperName": "approval", "TableEnumOrId": "quote", "OwnerPack": "sales", "State": "Active", "IsActive": true},
		},
		"cross object": {
			{"Id": "one", "DeveloperName": "Approval", "TableEnumOrId": "Quote", "OwnerPack": "sales", "State": "Active", "IsActive": true},
			{"Id": "two", "DeveloperName": "approval", "TableEnumOrId": "Account", "OwnerPack": "sales", "State": "Active", "IsActive": true},
		},
		"invalid boolean": {
			{"Id": "stale", "DeveloperName": "Stale", "TableEnumOrId": "Quote", "OwnerPack": "sales", "State": "Active", "IsActive": int64(2)},
		},
		"invalid state": {
			{"Id": "stale", "DeveloperName": "Stale", "TableEnumOrId": "Quote", "OwnerPack": "sales", "State": "Unknown", "IsActive": false},
		},
	} {
		t.Run(name, func(t *testing.T) {
			store := &approvalProcessReconcileTestStore{
				approvalProcessApplyTestStore: approvalProcessApplyTestStore{rows: map[string][]cp.ComponentRow{approvalProcessDefinitionTable: rows}},
				affected:                      1,
			}
			if err := reconcileApprovalProcessManagedSet(context.Background(), store, request, nil); err == nil || len(store.updates) != 0 {
				t.Fatalf("error/updates = %v/%#v", err, store.updates)
			}
		})
	}
}

type approvalProcessReconcileTestStore struct {
	approvalProcessApplyTestStore
	affected int64
}

func (s *approvalProcessReconcileTestStore) UpdateRows(_ context.Context, update cp.ComponentUpdate) (int64, error) {
	s.updates = append(s.updates, update)
	return s.affected, nil
}
