package controlplane

import (
	"bytes"
	"log"
	"strings"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	sqlmockdrv "github.com/DATA-DOG/go-sqlmock"
)

// TestVerifyStandardFieldSpecDrift_ReportsDrift uses Order.Type as the drift
// target because it is a picklist in the standard spec but NOT in the tenant
// spec — so it won't be skipped by the tenant-exclusion filter.
// Order.Type: spec=picklist/not-required/no-default with 4 options.
// Simulate live as a drifted row: string/not-required/NULL default, no options.
// account.Name: required string with no default (matches spec — silent).
func TestVerifyStandardFieldSpecDrift_ReportsDrift(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	rows := sqlmockdrv.NewRows([]string{"ApiName", "ApiName", "Type", "Required", "DefaultValue", "Options", "IsCustomized"}).
		AddRow("order", "Type", "string", false, nil, nil, false).
		AddRow("account", "Name", "string", true, nil, nil, false)

	mock.ExpectQuery("SELECT o\\.`ApiName`, f\\.`ApiName`, f\\.`Type`, f\\.`Required`, f\\.`DefaultValue`, f\\.`Options`, COALESCE\\(f\\.`IsCustomized`, 0\\) FROM `_System_Field` f JOIN `_System_Object` o").
		WithArgs("standard_object").
		WillReturnRows(rows)

	var buf bytes.Buffer
	oldOut := log.Writer()
	log.SetOutput(&buf)
	defer log.SetOutput(oldOut)

	// Order.Type: spec=picklist, live=string → structural drift → fatal error
	err = VerifyStandardFieldSpecDrift(db)
	if err == nil {
		t.Fatal("expected error for structural drift (Type mismatch), got nil")
	}
	if !strings.Contains(err.Error(), "structural drift") {
		t.Errorf("expected 'structural drift' in error, got: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("ExpectationsWereMet: %v", err)
	}

	out := buf.String()

	// Drift row must be flagged with a FATAL header + attribute diffs.
	if !strings.Contains(out, "spec drift: Order.Type") {
		t.Errorf("expected drift header for Order.Type, got:\n%s", out)
	}
	if !strings.Contains(out, "Type: spec=\"picklist\" live=\"string\"") {
		t.Errorf("expected Type drift line, got:\n%s", out)
	}

	// account.Name is consistent — no header for it.
	if strings.Contains(out, "spec drift: Account.Name") {
		t.Errorf("unexpected drift header for Account.Name, got:\n%s", out)
	}
}

// TestVerifyStandardFieldSpecDrift_CustomizedStructuralDriftWarns verifies that
// structural drift on an admin-customized field (IsCustomized=1) is downgraded
// to a warning instead of a fatal error.
func TestVerifyStandardFieldSpecDrift_CustomizedStructuralDriftWarns(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	rows := sqlmockdrv.NewRows([]string{"ApiName", "ApiName", "Type", "Required", "DefaultValue", "Options", "IsCustomized"}).
		AddRow("order", "Type", "string", false, nil, nil, true)

	mock.ExpectQuery("SELECT o\\.`ApiName`, f\\.`ApiName`, f\\.`Type`, f\\.`Required`, f\\.`DefaultValue`, f\\.`Options`, COALESCE\\(f\\.`IsCustomized`, 0\\) FROM `_System_Field` f JOIN `_System_Object` o").
		WithArgs("standard_object").
		WillReturnRows(rows)

	var buf bytes.Buffer
	oldOut := log.Writer()
	log.SetOutput(&buf)
	defer log.SetOutput(oldOut)

	if err := VerifyStandardFieldSpecDrift(db); err != nil {
		t.Fatalf("customized structural drift should warn, not fail: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("ExpectationsWereMet: %v", err)
	}

	out := buf.String()
	if strings.Contains(out, "[FATAL]") {
		t.Errorf("customized drift should not be fatal, got:\n%s", out)
	}
	if !strings.Contains(out, "spec drift: Order.Type") {
		t.Errorf("expected drift warning for Order.Type, got:\n%s", out)
	}
}
