package controlplane

import (
	"context"
	"database/sql"
	"fmt"
	"strings"
	"testing"

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

func sqlmockErrNoRows() error { return sql.ErrNoRows }

// Behavioral coverage for requireDeferFormulaViewPrecondition (the entry gate
// of the DeferFormulaViewRebuild contract): executed paths, not AST shape.

func deferPreconditionExpectBootstrapped(mock sqlmock.Sqlmock, fieldTableCount int) {
	mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM information_schema.TABLES").
		WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(fieldTableCount))
}

// deferPreconditionExpectReceiptTable covers the second entry probe: the
// write-ahead receipt needs `_System_BootstrapState` to exist before the
// running marker (which is what would otherwise create it) is ever stamped.
func deferPreconditionExpectReceiptTable(mock sqlmock.Sqlmock, tableCount int) {
	mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM information_schema.TABLES").
		WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(tableCount))
}

func deferPreconditionExpectFormulaRows(mock sqlmock.Sqlmock, objects ...string) {
	rows := sqlmock.NewRows([]string{"ApiName", "FormulaMode", "FormulaStorageMode", "Formula", "ProjectionConfig"})
	for _, object := range objects {
		rows.AddRow(object, "active", "", "1 + 1", "")
	}
	mock.ExpectQuery("SELECT o.ApiName").WillReturnRows(rows)
}

func deferPreconditionExpectViewExists(mock sqlmock.Sqlmock, exists bool) {
	count := 0
	if exists {
		count = 1
	}
	mock.ExpectQuery("SELECT COUNT\\(\\*\\)[\\s\\S]*information_schema.VIEWS").
		WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(count))
}

func TestDeferPreconditionPassesOnHealthyInventory(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	deferPreconditionExpectBootstrapped(mock, 1)
	deferPreconditionExpectReceiptTable(mock, 1)
	deferPreconditionExpectFormulaRows(mock, "Widget")
	deferPreconditionExpectViewExists(mock, true)
	mock.ExpectQuery("SELECT 1 FROM `v_widget` LIMIT 1").
		WillReturnRows(sqlmock.NewRows([]string{"1"}).AddRow(1))

	if err := requireDeferFormulaViewPrecondition(context.Background(), db); err != nil {
		t.Fatalf("precondition = %v, want pass on healthy inventory", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
}

func TestDeferPreconditionFailsClosedOnMissingView(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	deferPreconditionExpectBootstrapped(mock, 1)
	deferPreconditionExpectReceiptTable(mock, 1)
	deferPreconditionExpectFormulaRows(mock, "Widget")
	deferPreconditionExpectViewExists(mock, false)

	err = requireDeferFormulaViewPrecondition(context.Background(), db)
	if err == nil {
		t.Fatal("precondition = nil, want fail-closed on missing view")
	}
	if !strings.Contains(err.Error(), "does not exist") || !strings.Contains(err.Error(), "v_widget") {
		t.Fatalf("error = %v, want missing-view cause naming the view", err)
	}
}

func TestDeferPreconditionFailsClosedOnUnreadableView(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	deferPreconditionExpectBootstrapped(mock, 1)
	deferPreconditionExpectReceiptTable(mock, 1)
	deferPreconditionExpectFormulaRows(mock, "Widget")
	deferPreconditionExpectViewExists(mock, true)
	mock.ExpectQuery("SELECT 1 FROM `v_widget` LIMIT 1").
		WillReturnError(fmt.Errorf("definer does not exist"))

	err = requireDeferFormulaViewPrecondition(context.Background(), db)
	if err == nil {
		t.Fatal("precondition = nil, want fail-closed on unreadable view")
	}
	if !strings.Contains(err.Error(), "not readable") {
		t.Fatalf("error = %v, want unreadable cause", err)
	}
}

func TestDeferPreconditionRequiresBootstrappedDatabase(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	deferPreconditionExpectBootstrapped(mock, 0)

	err = requireDeferFormulaViewPrecondition(context.Background(), db)
	if err == nil {
		t.Fatal("precondition = nil, want bootstrapped-first error on greenfield")
	}
	if !strings.Contains(err.Error(), "already-bootstrapped") {
		t.Fatalf("error = %v, want bootstrapped-first guidance", err)
	}
}

func TestDeferPreconditionRequiresReceiptTable(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	deferPreconditionExpectBootstrapped(mock, 1)
	deferPreconditionExpectReceiptTable(mock, 0)

	err = requireDeferFormulaViewPrecondition(context.Background(), db)
	if err == nil {
		t.Fatal("precondition = nil, want fail-fast when the receipt has nowhere durable to live")
	}
	if !strings.Contains(err.Error(), "_System_BootstrapState") || !strings.Contains(err.Error(), "standard apply") {
		t.Fatalf("error = %v, want receipt-table guidance", err)
	}
}

func TestFormulaViewRebuildOwedReceiptLifecycle(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()

	mock.ExpectExec("INSERT INTO `_System_BootstrapState`").
		WithArgs("formula_view_rebuild_owed").
		WillReturnResult(sqlmock.NewResult(0, 1))
	if err := markFormulaViewRebuildOwed(context.Background(), db); err != nil {
		t.Fatalf("markFormulaViewRebuildOwed = %v", err)
	}

	mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM information_schema.TABLES").
		WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
	mock.ExpectQuery("SELECT `Status` FROM `_System_BootstrapState`").
		WithArgs("formula_view_rebuild_owed").
		WillReturnRows(sqlmock.NewRows([]string{"Status"}).AddRow("pending"))
	owed, err := FormulaViewRebuildOwed(context.Background(), db)
	if err != nil || !owed {
		t.Fatalf("FormulaViewRebuildOwed = (%v, %v), want (true, nil)", owed, err)
	}

	mock.ExpectExec("UPDATE `_System_BootstrapState` SET `Status`='cleared'").
		WithArgs("formula_view_rebuild_owed").
		WillReturnResult(sqlmock.NewResult(0, 1))
	if err := clearFormulaViewRebuildOwed(context.Background(), db); err != nil {
		t.Fatalf("clearFormulaViewRebuildOwed = %v", err)
	}

	mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM information_schema.TABLES").
		WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
	mock.ExpectQuery("SELECT `Status` FROM `_System_BootstrapState`").
		WithArgs("formula_view_rebuild_owed").
		WillReturnRows(sqlmock.NewRows([]string{"Status"}).AddRow("cleared"))
	owed, err = FormulaViewRebuildOwed(context.Background(), db)
	if err != nil || owed {
		t.Fatalf("FormulaViewRebuildOwed after clear = (%v, %v), want (false, nil)", owed, err)
	}

	mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM information_schema.TABLES").
		WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
	mock.ExpectQuery("SELECT `Status` FROM `_System_BootstrapState`").
		WithArgs("formula_view_rebuild_owed").
		WillReturnError(sqlmockErrNoRows())
	owed, err = FormulaViewRebuildOwed(context.Background(), db)
	if err != nil || owed {
		t.Fatalf("FormulaViewRebuildOwed with no row = (%v, %v), want (false, nil)", owed, err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
}
