package persistence

import (
	"context"
	"regexp"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/nexuscrm/shared/pkg/models"
)

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

	repo := NewMetadataRepository(db)
	rows := sqlmock.NewRows([]string{
		"Id", "DefinitionId", "MasterLabel", "VersionNumber", "Status", "Description",
		"ProcessType", "Metadata", "Source", "IsDeleted", "CreatedDate", "LastModifiedDate",
	}).AddRow(
		"flow-version-1",
		"flow-definition-1",
		"Campaign Default Member Statuses",
		1,
		"Active",
		"desc",
		"AutoLaunchedFlow",
		`{"start":{"triggerType":"RecordAfterSave","recordTriggerType":"Create","object":"Campaign"}}`,
		"custom",
		false,
		nil,
		nil,
	)

	mock.ExpectQuery(regexp.QuoteMeta(
		`SELECT Id, DefinitionId, MasterLabel, VersionNumber, Status, Description, ProcessType, Metadata,
		Source, IsDeleted, CreatedDate, LastModifiedDate FROM Flow WHERE Id = ?`,
	)).WithArgs("flow-version-1").WillReturnRows(rows)

	version, err := repo.GetFlowVersion(context.Background(), "flow-version-1")
	if err != nil {
		t.Fatalf("GetFlowVersion: %v", err)
	}
	if version == nil {
		t.Fatal("expected flow version, got nil")
	}
	if version.Source != "custom" {
		t.Fatalf("Source = %q, want custom", version.Source)
	}
	if version.CreatedDate != "" {
		t.Fatalf("CreatedDate = %q, want empty string", version.CreatedDate)
	}
	if version.LastModifiedDate != "" {
		t.Fatalf("LastModifiedDate = %q, want empty string", version.LastModifiedDate)
	}
	if version.Metadata.Start == nil {
		t.Fatal("expected hydrated start metadata")
	}
	if version.Metadata.Start.TriggerType != "RecordAfterSave" {
		t.Fatalf("TriggerType = %q, want RecordAfterSave", version.Metadata.Start.TriggerType)
	}

	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("ExpectationsWereMet: %v", err)
	}
}

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

	def := &models.FlowDefinition{
		Id:            "flow-1",
		DeveloperName: "Foo",
		MasterLabel:   "Foo",
		ProcessType:   "AutoLaunchedFlow",
	}

	mock.ExpectExec(`INSERT INTO FlowDefinition`).
		WithArgs(
			"flow-1", "Foo", "Foo", "", "AutoLaunchedFlow",
			"", "", nil, nil, false, false,
			sqlmock.AnyArg(), sqlmock.AnyArg(),
		).
		WillReturnResult(sqlmock.NewResult(1, 1))

	if err := repo.CreateFlowDefinition(context.Background(), def); err != nil {
		t.Fatalf("CreateFlowDefinition: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("ExpectationsWereMet: %v", err)
	}
}

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

	mock.ExpectExec(`UPDATE FlowDefinition SET IsCustomized = 1, LastModifiedDate = \? WHERE Id = \?`).
		WithArgs(sqlmock.AnyArg(), "flow-2").
		WillReturnResult(sqlmock.NewResult(1, 1))

	if err := repo.MarkFlowDefinitionCustomized(context.Background(), "flow-2"); err != nil {
		t.Fatalf("MarkFlowDefinitionCustomized: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("ExpectationsWereMet: %v", err)
	}
}

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

	mock.ExpectExec(`UPDATE FlowDefinition\s+SET IsDeleted = 1, IsCustomized = 1, ActiveVersionId = NULL, LastModifiedDate = \?\s+WHERE Id = \?`).
		WithArgs(sqlmock.AnyArg(), "flow-42").
		WillReturnResult(sqlmock.NewResult(0, 1))

	if err := repo.DeleteFlowDefinition(context.Background(), "flow-42"); err != nil {
		t.Fatalf("DeleteFlowDefinition: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("ExpectationsWereMet: %v", err)
	}
}

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

	label := "Updated"
	description := "patched"
	patch := &models.FlowDefinitionPatch{
		MasterLabel: &label,
		Description: &description,
	}

	mock.ExpectExec(`UPDATE FlowDefinition SET MasterLabel = \?, Description = \?, LastModifiedDate = \? WHERE Id = \?`).
		WithArgs("Updated", "patched", sqlmock.AnyArg(), "flow-7").
		WillReturnResult(sqlmock.NewResult(0, 1))

	if err := repo.PatchFlowDefinition(context.Background(), "flow-7", patch); err != nil {
		t.Fatalf("PatchFlowDefinition: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("ExpectationsWereMet: %v", err)
	}
}

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

	version := &models.FlowVersion{
		Id:            "flow-ver-1",
		DefinitionId:  "flow-def-1",
		VersionNumber: 1,
		Status:        "Draft",
		Description:   "seeded",
		ProcessType:   "AutoLaunchedFlow",
		MetadataRaw:   []byte(`{"label":"Seeded Flow"}`),
	}

	mock.ExpectExec(`INSERT INTO Flow .*'standard'.*`).
		WithArgs(
			"flow-ver-1", "flow-def-1", 1, "Draft",
			"seeded", "AutoLaunchedFlow", sqlmock.AnyArg(),
			sqlmock.AnyArg(), sqlmock.AnyArg(),
		).
		WillReturnResult(sqlmock.NewResult(1, 1))

	if err := repo.CreateSeededFlowVersion(context.Background(), version); err != nil {
		t.Fatalf("CreateSeededFlowVersion: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("ExpectationsWereMet: %v", err)
	}
}

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

	version := &models.FlowVersion{
		Id:            "flow-ver-tenant-1",
		DefinitionId:  "flow-def-tenant-1",
		VersionNumber: 1,
		Status:        "Draft",
		Description:   "tenant overlay flow",
		ProcessType:   "AutoLaunchedFlow",
		MetadataRaw:   []byte(`{"label":"Tenant Flow"}`),
	}

	mock.ExpectExec(`INSERT INTO Flow .*'custom'.*`).
		WithArgs(
			"flow-ver-tenant-1", "flow-def-tenant-1", 1, "Draft",
			"tenant overlay flow", "AutoLaunchedFlow", sqlmock.AnyArg(),
			sqlmock.AnyArg(), sqlmock.AnyArg(),
		).
		WillReturnResult(sqlmock.NewResult(1, 1))

	if err := repo.CreateTenantFlowVersion(context.Background(), version); err != nil {
		t.Fatalf("CreateTenantFlowVersion: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("ExpectationsWereMet: %v", err)
	}
}

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

	rows := sqlmock.NewRows([]string{
		"Id", "DeveloperName", "MasterLabel", "Description", "ProcessType",
		"TriggerType", "TriggerObjectOrEvent", "ActiveVersionId", "LatestVersionId", "IsDeleted", "IsCustomized",
		"CreatedDate", "LastModifiedDate",
	}).AddRow(
		"flow-99", "Foo", "Foo", "", "AutoLaunchedFlow",
		"RecordBeforeSave", "Lead", "", "", false, true,
		"2026-04-23T00:00:00Z", "2026-04-23T00:00:00Z",
	)

	mock.ExpectQuery(`SELECT .* FROM FlowDefinition WHERE DeveloperName = \?`).
		WithArgs("Foo").
		WillReturnRows(rows)

	got, err := repo.GetFlowDefinitionByName(context.Background(), "Foo")
	if err != nil {
		t.Fatalf("GetFlowDefinitionByName: %v", err)
	}
	if got == nil {
		t.Fatal("expected flow definition, got nil")
	}
	if got.DeveloperName != "Foo" {
		t.Errorf("DeveloperName = %q, want Foo", got.DeveloperName)
	}
	if !got.IsCustomized {
		t.Fatal("expected IsCustomized=true")
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("ExpectationsWereMet: %v", err)
	}
}
