package dialectsfcrm

import (
	"bytes"
	"context"
	"strings"
	"testing"

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

func TestApprovalProcessManagedVerifyMatchesCanonicalRowsAndReportsDrift(t *testing.T) {
	request := approvalProcessManagedRequestForTest(t)
	reader := approvalProcessVerifyReaderForTest(t, request)
	reader.rows[approvalProcessNodeTable][0]["IsDeleted"] = nil
	reader.rows[approvalProcessActionTable][0]["IsDeleted"] = nil
	reader.rows[approvalProcessDefinitionTable][0]["IsActive"] = int64(1)
	reader.rows[approvalProcessDefinitionTable][0]["FinalApprovalRecordLock"] = int64(0)
	expected, live, err := verifyApprovalProcessManagedProjection(context.Background(), reader, request)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(expected, live) {
		t.Fatalf("expected/live mismatch:\n%s\n%s", expected, live)
	}
	if len(reader.queries) != 3 || reader.queries[0].Equal["DeveloperName"] != "Quote_Approval" || len(reader.queries[1].Equal) != 1 || len(reader.queries[2].Equal) != 1 || reader.queries[2].Equal["ProcessDefinitionId"] != "definition" {
		t.Fatalf("queries = %#v", reader.queries)
	}

	drifted := approvalProcessVerifyReaderForTest(t, request)
	drifted.rows[approvalProcessNodeTable][0]["Name"] = "drifted"
	expected, live, err = verifyApprovalProcessManagedProjection(context.Background(), drifted, request)
	if err != nil {
		t.Fatal(err)
	}
	if bytes.Equal(expected, live) {
		t.Fatal("node drift produced an equal projection")
	}
}

func TestApprovalProcessManagedVerifyFailsClosed(t *testing.T) {
	request := approvalProcessManagedRequestForTest(t)
	if _, _, err := verifyApprovalProcessManagedProjection(context.Background(), nil, request); err == nil {
		t.Fatal("nil reader was accepted")
	}
	bad := request
	bad.PackName = " sales"
	reader := &approvalProcessVerifyTestReader{}
	if _, _, err := verifyApprovalProcessManagedProjection(context.Background(), reader, bad); err == nil || len(reader.queries) != 0 {
		t.Fatalf("untrusted request error/reads = %v/%d", err, len(reader.queries))
	}
	if _, _, err := verifyApprovalProcessManagedProjection(context.Background(), &approvalProcessVerifyTestReader{}, request); err == nil || !strings.Contains(err.Error(), "resolves to 0") {
		t.Fatalf("missing parent error = %v", err)
	}
	dangling := approvalProcessVerifyReaderForTest(t, request)
	dangling.rows[approvalProcessActionTable][0]["ProcessNodeId"] = "missing"
	if _, _, err := verifyApprovalProcessManagedProjection(context.Background(), dangling, request); err == nil || !strings.Contains(err.Error(), "missing active ProcessNode") {
		t.Fatalf("dangling action error = %v", err)
	}
}

type approvalProcessVerifyTestReader struct {
	rows    map[string][]cp.ComponentRow
	queries []cp.ComponentRowQuery
}

func (r *approvalProcessVerifyTestReader) ReadRows(_ context.Context, query cp.ComponentRowQuery) ([]cp.ComponentRow, error) {
	r.queries = append(r.queries, query)
	return r.rows[query.Table], nil
}

func approvalProcessVerifyReaderForTest(t *testing.T, request cp.ComponentManagedRequest) *approvalProcessVerifyTestReader {
	t.Helper()
	plan, err := decodeApprovalProcessManagedRequest(request)
	if err != nil {
		t.Fatal(err)
	}
	parent := approvalProcessManagedParentValues(plan.Parent)
	parent["Id"] = "definition"
	rows := map[string][]cp.ComponentRow{approvalProcessDefinitionTable: {parent}}
	nodeIDs := make(map[ApprovalProcessNodeBusinessKey]string, len(plan.Nodes))
	for index := len(plan.Nodes) - 1; index >= 0; index-- {
		node := plan.Nodes[index]
		id := "node-" + strings.ToLower(node.BusinessKey.DeveloperName)
		nodeIDs[node.BusinessKey] = id
		values := approvalProcessManagedNodeValues("definition", node)
		values["Id"] = id
		rows[approvalProcessNodeTable] = append(rows[approvalProcessNodeTable], values)
	}
	for index := len(plan.Actions) - 1; index >= 0; index-- {
		values, err := approvalProcessManagedActionValues("definition", nodeIDs, plan.Actions[index])
		if err != nil {
			t.Fatal(err)
		}
		rows[approvalProcessActionTable] = append(rows[approvalProcessActionTable], values)
	}
	return &approvalProcessVerifyTestReader{rows: rows}
}
