package bootstrapimpl

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

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

func TestDropGroupMemberUserFK_AlreadyAppliedSkips(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery(regexp.QuoteMeta("SELECT 1 FROM _System_Migration WHERE Id = ?")).
		WithArgs(migrationIdDropGroupMemberUserFK).
		WillReturnRows(sqlmock.NewRows([]string{"_"}).AddRow(1))

	if err := DropGroupMemberUserFK(context.Background(), db); err != nil {
		t.Fatalf("DropGroupMemberUserFK (already applied) = error %v, want nil", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations unmet: %v", err)
	}
}

// TestDropGroupMemberUserFK_ConstraintAlreadyAbsentStamps verifies the
// FK-already-absent path (fresh instance OR manual removal OR prior partial
// run): the migration stamps without touching the schema.
func TestDropGroupMemberUserFK_ConstraintAlreadyAbsentStamps(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery(regexp.QuoteMeta("SELECT 1 FROM _System_Migration WHERE Id = ?")).
		WithArgs(migrationIdDropGroupMemberUserFK).
		WillReturnRows(sqlmock.NewRows([]string{"_"}))

	mock.ExpectQuery(regexp.QuoteMeta("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE")).
		WillReturnRows(sqlmock.NewRows([]string{"CONSTRAINT_NAME"}))

	mock.ExpectExec(regexp.QuoteMeta("INSERT INTO _System_Migration")).
		WillReturnResult(sqlmock.NewResult(1, 1))

	if err := DropGroupMemberUserFK(context.Background(), db); err != nil {
		t.Fatalf("DropGroupMemberUserFK (constraint absent) = error %v, want nil", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations unmet: %v", err)
	}
}

func TestDropGroupMemberUserFK_DropsConstraintAndStamps(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery(regexp.QuoteMeta("SELECT 1 FROM _System_Migration WHERE Id = ?")).
		WithArgs(migrationIdDropGroupMemberUserFK).
		WillReturnRows(sqlmock.NewRows([]string{"_"}))

	mock.ExpectQuery(regexp.QuoteMeta("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE")).
		WillReturnRows(sqlmock.NewRows([]string{"CONSTRAINT_NAME"}).AddRow("fk_2"))

	mock.ExpectExec(regexp.QuoteMeta("ALTER TABLE `GroupMember` DROP FOREIGN KEY `fk_2`")).
		WillReturnResult(sqlmock.NewResult(0, 0))

	mock.ExpectExec(regexp.QuoteMeta("INSERT INTO _System_Migration")).
		WillReturnResult(sqlmock.NewResult(1, 1))

	if err := DropGroupMemberUserFK(context.Background(), db); err != nil {
		t.Fatalf("DropGroupMemberUserFK = error %v, want nil", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations unmet: %v", err)
	}
}

func TestDropGroupMemberUserFK_LookupErrorPropagates(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery(regexp.QuoteMeta("SELECT 1 FROM _System_Migration WHERE Id = ?")).
		WithArgs(migrationIdDropGroupMemberUserFK).
		WillReturnRows(sqlmock.NewRows([]string{"_"}))

	mock.ExpectQuery(regexp.QuoteMeta("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE")).
		WillReturnError(sql.ErrConnDone)

	if err := DropGroupMemberUserFK(context.Background(), db); err == nil {
		t.Fatal("DropGroupMemberUserFK = nil, want error (lookup failure)")
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations unmet: %v", err)
	}
}

func TestDropGroupMemberUserFK_DropErrorPropagates(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery(regexp.QuoteMeta("SELECT 1 FROM _System_Migration WHERE Id = ?")).
		WithArgs(migrationIdDropGroupMemberUserFK).
		WillReturnRows(sqlmock.NewRows([]string{"_"}))

	mock.ExpectQuery(regexp.QuoteMeta("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE")).
		WillReturnRows(sqlmock.NewRows([]string{"CONSTRAINT_NAME"}).AddRow("fk_2"))

	mock.ExpectExec(regexp.QuoteMeta("ALTER TABLE `GroupMember` DROP FOREIGN KEY `fk_2`")).
		WillReturnError(fmt.Errorf("cannot drop FK"))

	if err := DropGroupMemberUserFK(context.Background(), db); err == nil {
		t.Fatal("DropGroupMemberUserFK = nil, want error (drop failure)")
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations unmet: %v", err)
	}
}

func TestDropGroupMemberUserFK_StampErrorPropagates(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery(regexp.QuoteMeta("SELECT 1 FROM _System_Migration WHERE Id = ?")).
		WithArgs(migrationIdDropGroupMemberUserFK).
		WillReturnRows(sqlmock.NewRows([]string{"_"}))

	mock.ExpectQuery(regexp.QuoteMeta("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE")).
		WillReturnRows(sqlmock.NewRows([]string{"CONSTRAINT_NAME"}).AddRow("fk_2"))

	mock.ExpectExec(regexp.QuoteMeta("ALTER TABLE `GroupMember` DROP FOREIGN KEY `fk_2`")).
		WillReturnResult(sqlmock.NewResult(0, 0))

	mock.ExpectExec(regexp.QuoteMeta("INSERT INTO _System_Migration")).
		WillReturnError(fmt.Errorf("stamp insert failed"))

	err = DropGroupMemberUserFK(context.Background(), db)
	if err == nil {
		t.Fatal("DropGroupMemberUserFK = nil, want error (stamp failure)")
	}
	if !strings.Contains(err.Error(), "stamp insert failed") {
		t.Fatalf("error = %v, want 'stamp insert failed' in message", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations unmet: %v", err)
	}
}
