package vercel

import (
	"context"
	"database/sql"
	"log"
	"os"
	"testing"
	"time"

	"github.com/ory/dockertest/v3"
	"github.com/ory/dockertest/v3/docker"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/suite"
	"github.com/tidbcloud/ecosystem-service/internal/storage/migrate"
	"github.com/tidbcloud/pkgv2/gormutil"
	"gorm.io/gorm"
)

type EntityTestSuite struct {
	suite.Suite
	ConfigurationDAO         VercelIntegrationConfigurationDAO
	TiDBVercelConnectionDAO  ClusterConnectionDAO
	EnvironmentIdentifierDAO EnvironmentIdentifierDAO
	ComposeEntityDAO         ComposeEntityDAO
}

var (
	createTime = time.Date(1999, 1, 12, 12, 00, 00, 0, time.UTC)
	globalDB   *gorm.DB
)

// Start dockerDB
func TestMain(m *testing.M) {
	// uses a sensible default on windows (tcp/http) and linux/osx (socket)
	pool, err := dockertest.NewPool("")
	if err != nil {
		log.Fatalf("Could not connect to docker: %s", err)
	}

	// pulls an image, creates a container based on it and runs it
	resource, err := pool.RunWithOptions(&dockertest.RunOptions{
		Repository:   "mysql",
		Tag:          "5.7",
		Env:          []string{"MYSQL_ROOT_PASSWORD=secret"},
		ExposedPorts: []string{"50505"},
		PortBindings: map[docker.Port][]docker.PortBinding{
			"3306/tcp": {{HostIP: "127.0.0.1", HostPort: "50505"}},
		},
		Platform: "linux/x86_64",
	}, func(config *docker.HostConfig) {
		config.AutoRemove = true
		config.RestartPolicy = docker.RestartPolicy{
			Name: "no",
		}
	})

	if err != nil {
		log.Fatalf("Could not start resource: %s", err)
	}

	connCfg := gormutil.Config{
		User:              "root",
		Host:              "127.0.0.1",
		Database:          "mysql",
		Password:          "secret",
		Port:              50505,
		EnableDetailedLog: true,
		LogLevel:          "info",
	}

	// exponential backoff-retry, because the application in the container might not be ready to accept connections yet
	if err := pool.Retry(func() error {
		var err error
		database, err := gormutil.Connect(connCfg)
		if err != nil {
			return err
		}

		globalDB = database

		return nil
	}); err != nil {
		log.Fatalf("Could not connect to database: %s", err)
	}

	code := m.Run()

	// You can't defer this because os.Exit doesn't care for defer
	if err := pool.Purge(resource); err != nil {
		log.Fatalf("Could not purge resource: %s", err)
	}

	os.Exit(code)
}

func (suite *EntityTestSuite) SetupSuite() {
	suite.ConfigurationDAO = NewVercelIntegrationConfigurationDAO(globalDB)
	suite.TiDBVercelConnectionDAO = NewClusterConnectionDAO(globalDB)
	suite.EnvironmentIdentifierDAO = NewEnvironmentIdentifierDAO(globalDB)
	suite.ComposeEntityDAO = NewComposeEntityDAO(
		globalDB,
		NewDataAppEnvironmentIdentifierDAO(globalDB),
		NewDataAppConnectionDAO(globalDB),
		suite.EnvironmentIdentifierDAO,
		suite.TiDBVercelConnectionDAO)
}

func (suite *EntityTestSuite) SetupTest() {
	if err := migrate.GormAutoMigrateAndPrint(globalDB, MigrateTables); err != nil {
		suite.FailNow("Failed to migrate tables", err)
	}
}

func (suite *EntityTestSuite) TearDownTest() {
	for _, table := range MigrateTables {
		if err := globalDB.Migrator().DropTable(table); err != nil {
			suite.FailNow("Failed to drop tables", err)
		}
	}
}

func (suite *EntityTestSuite) TestADDConnection() {
	ctx := context.TODO()
	err := suite.ConfigurationDAO.AddVercelIntegrationConfiguration(ctx, VercelIntegrationConfiguration{
		ID:             "AFAR",
		ApiAccessToken: "S&DAF&AF",
		UserID:         "1",
		CreatedAt:      createTime,
		UpdatedAt:      createTime,
	})
	assert.NoError(suite.T(), err)

	_, err = suite.TiDBVercelConnectionDAO.AddVercelConnection(ctx, ClusterConnection{
		ID:                               1,
		VercelProjectID:                  "1",
		TiDBOrgID:                        1,
		TiDBProjectID:                    1,
		TiDBClusterID:                    1,
		VercelIntegrationConfigurationID: "AFAR",
		CreatedAt:                        createTime,
		UpdatedAt:                        createTime,
	})
	assert.NoError(suite.T(), err)

	c, err := suite.TiDBVercelConnectionDAO.GetVercelConnection(ctx, 1, "1")
	assert.NoError(suite.T(), err)

	assert.Equal(suite.T(), &ClusterConnection{
		ID:                               1,
		CreatedAt:                        createTime,
		UpdatedAt:                        createTime,
		FrameworkType:                    "General",
		VercelProjectID:                  "1",
		TiDBOrgID:                        1,
		TiDBProjectID:                    1,
		TiDBClusterID:                    1,
		VercelIntegrationConfigurationID: "AFAR",
		VercelIntegrationConfiguration: VercelIntegrationConfiguration{
			ID:             "AFAR",
			CreatedAt:      createTime,
			UpdatedAt:      createTime,
			UserID:         "1",
			ApiAccessToken: "S&DAF&AF",
		},
		Database:                     "test",
		BranchEnvironmentIdentifiers: []BranchEnvironmentIdentifier{},
	}, c)
}

func (suite *EntityTestSuite) TestADDConnectionWithoutIntegration() {
	ctx := context.TODO()
	_, err := suite.TiDBVercelConnectionDAO.AddVercelConnection(ctx, ClusterConnection{
		VercelProjectID:                  "1",
		TiDBOrgID:                        1,
		TiDBProjectID:                    1,
		TiDBClusterID:                    1,
		VercelIntegrationConfigurationID: "1",
	})
	assert.EqualError(suite.T(), err, "Error 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`mysql`.`cluster_connections`, CONSTRAINT `fk_cluster_connections_vercel_integration_configuration` FOREIGN KEY (`vercel_integration_configuration_id`) REFERENCES `vercel_integration_configura)")

	_, err = suite.TiDBVercelConnectionDAO.AddVercelConnection(ctx, ClusterConnection{
		VercelProjectID: "1",
		TiDBOrgID:       1,
		TiDBProjectID:   1,
		TiDBClusterID:   1,
	})
	assert.EqualError(suite.T(), err, "Error 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`mysql`.`cluster_connections`, CONSTRAINT `fk_cluster_connections_vercel_integration_configuration` FOREIGN KEY (`vercel_integration_configuration_id`) REFERENCES `vercel_integration_configura)")
}

func (suite *EntityTestSuite) TestADDIdentifierWithoutConnection() {
	ctx := context.TODO()
	err := suite.EnvironmentIdentifierDAO.AddEnvironmentIdentifier(ctx, EnvironmentIdentifier{
		TiDBHostID: sql.NullString{
			String: "host",
		},
		TiDBPortID: sql.NullString{
			String: "port",
		},
		TiDBUserID: sql.NullString{
			String: "user",
		},
		TiDBPasswordID: sql.NullString{
			String: "pass",
		},
		ClusterConnectionID: 1,
	})
	assert.EqualError(suite.T(), err, "Error 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`mysql`.`environment_identifiers`, CONSTRAINT `fk_cluster_connections_environment_identifier` FOREIGN KEY (`cluster_connection_id`) REFERENCES `cluster_connections` (`id`))")

	err = suite.EnvironmentIdentifierDAO.AddEnvironmentIdentifier(ctx, EnvironmentIdentifier{
		TiDBHostID: sql.NullString{
			String: "host",
		},
		TiDBPortID: sql.NullString{
			String: "port",
		},
		TiDBUserID: sql.NullString{
			String: "user",
		},
		TiDBPasswordID: sql.NullString{
			String: "pass",
		},
	})
	assert.EqualError(suite.T(), err, "Error 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`mysql`.`environment_identifiers`, CONSTRAINT `fk_cluster_connections_environment_identifier` FOREIGN KEY (`cluster_connection_id`) REFERENCES `cluster_connections` (`id`))")
}

func (suite *EntityTestSuite) TestDeleteConnection() {
	ctx := context.TODO()
	err := suite.ConfigurationDAO.AddVercelIntegrationConfiguration(ctx, VercelIntegrationConfiguration{
		ID:             "AFAR",
		ApiAccessToken: "S&DAF&AF",
		UserID:         "1",
		CreatedAt:      createTime,
		UpdatedAt:      createTime,
	})
	assert.NoError(suite.T(), err)

	_, err = suite.TiDBVercelConnectionDAO.AddVercelConnection(ctx, ClusterConnection{
		ID:                               1,
		VercelProjectID:                  "1",
		TiDBOrgID:                        1,
		TiDBProjectID:                    1,
		TiDBClusterID:                    1,
		VercelIntegrationConfigurationID: "AFAR",
		CreatedAt:                        createTime,
		UpdatedAt:                        createTime,
	})
	assert.NoError(suite.T(), err)

	err = suite.EnvironmentIdentifierDAO.AddEnvironmentIdentifier(ctx, EnvironmentIdentifier{
		TiDBHostID: sql.NullString{
			String: "host",
		},
		TiDBPortID: sql.NullString{
			String: "port",
		},
		TiDBUserID: sql.NullString{
			String: "user",
		},
		TiDBPasswordID: sql.NullString{
			String: "pass",
		},
		ClusterConnectionID: 1,
	})
	assert.NoError(suite.T(), err)

	err = suite.ComposeEntityDAO.DeleteClusterConnection(ctx, 1, "1")
	assert.NoError(suite.T(), err)

	var count int64
	err = globalDB.Model(&ClusterConnection{}).Count(&count).Error
	assert.NoError(suite.T(), err)
	assert.Equal(suite.T(), count, int64(0))

	err = globalDB.Model(&EnvironmentIdentifier{}).Count(&count).Error
	assert.NoError(suite.T(), err)
	assert.Equal(suite.T(), count, int64(0))
}

func (suite *EntityTestSuite) TestDeleteConnectionByVercelProjectIDs() {
	ctx := context.TODO()
	err := suite.ConfigurationDAO.AddVercelIntegrationConfiguration(ctx, VercelIntegrationConfiguration{
		ID:             "AFAR",
		ApiAccessToken: "S&DAF&AF",
		UserID:         "1",
		CreatedAt:      createTime,
		UpdatedAt:      createTime,
	})
	assert.NoError(suite.T(), err)

	_, err = suite.TiDBVercelConnectionDAO.AddVercelConnection(ctx, ClusterConnection{
		ID:                               1,
		VercelProjectID:                  "1",
		TiDBOrgID:                        1,
		TiDBProjectID:                    1,
		TiDBClusterID:                    1,
		VercelIntegrationConfigurationID: "AFAR",
		CreatedAt:                        createTime,
		UpdatedAt:                        createTime,
	})
	assert.NoError(suite.T(), err)

	err = suite.EnvironmentIdentifierDAO.AddEnvironmentIdentifier(ctx, EnvironmentIdentifier{
		TiDBHostID: sql.NullString{
			String: "host",
		},
		TiDBPortID: sql.NullString{
			String: "port",
		},
		TiDBUserID: sql.NullString{
			String: "user",
		},
		TiDBPasswordID: sql.NullString{
			String: "pass",
		},
		ClusterConnectionID: 1,
	})
	assert.NoError(suite.T(), err)

	_, err = suite.TiDBVercelConnectionDAO.AddVercelConnection(ctx, ClusterConnection{
		ID:                               2,
		VercelProjectID:                  "2",
		TiDBOrgID:                        2,
		TiDBProjectID:                    2,
		TiDBClusterID:                    2,
		VercelIntegrationConfigurationID: "AFAR",
		CreatedAt:                        createTime,
		UpdatedAt:                        createTime,
	})
	assert.NoError(suite.T(), err)

	err = suite.EnvironmentIdentifierDAO.AddEnvironmentIdentifier(ctx, EnvironmentIdentifier{
		TiDBHostID: sql.NullString{
			String: "host",
		},
		TiDBPortID: sql.NullString{
			String: "port",
		},
		TiDBUserID: sql.NullString{
			String: "user",
		},
		TiDBPasswordID: sql.NullString{
			String: "pass",
		},
		ClusterConnectionID: 2,
	})
	assert.NoError(suite.T(), err)

	err = suite.ComposeEntityDAO.DeleteVercelConnectionsByVercelProjectIDs(ctx, []string{"1", "2"})
	assert.NoError(suite.T(), err)

	var count int64
	err = globalDB.Model(&ClusterConnection{}).Count(&count).Error
	assert.NoError(suite.T(), err)
	assert.Equal(suite.T(), count, int64(0))

	err = globalDB.Model(&EnvironmentIdentifier{}).Count(&count).Error
	assert.NoError(suite.T(), err)
	assert.Equal(suite.T(), count, int64(0))
}

func (suite *EntityTestSuite) TestDeleteIntegrationConfig() {
	ctx := context.TODO()
	var err = suite.ConfigurationDAO.AddVercelIntegrationConfiguration(ctx, VercelIntegrationConfiguration{
		ID:             "AFAR",
		ApiAccessToken: "S&DAF&AF",
		CreatedAt:      createTime,
		UpdatedAt:      createTime,
	})
	assert.NoError(suite.T(), err)

	_, err = suite.TiDBVercelConnectionDAO.AddVercelConnection(ctx, ClusterConnection{
		ID:                               1,
		VercelProjectID:                  "1",
		TiDBOrgID:                        1,
		TiDBProjectID:                    1,
		TiDBClusterID:                    1,
		VercelIntegrationConfigurationID: "AFAR",
		CreatedAt:                        createTime,
		UpdatedAt:                        createTime,
	})
	assert.NoError(suite.T(), err)

	_, err = suite.TiDBVercelConnectionDAO.AddVercelConnection(ctx, ClusterConnection{
		ID:                               2,
		VercelProjectID:                  "2",
		TiDBOrgID:                        2,
		TiDBProjectID:                    2,
		TiDBClusterID:                    2,
		VercelIntegrationConfigurationID: "AFAR",
		CreatedAt:                        createTime,
		UpdatedAt:                        createTime,
	})
	assert.NoError(suite.T(), err)

	err = suite.EnvironmentIdentifierDAO.AddEnvironmentIdentifier(ctx, EnvironmentIdentifier{
		TiDBHostID: sql.NullString{
			String: "host",
		},
		TiDBPortID: sql.NullString{
			String: "port",
		},
		TiDBUserID: sql.NullString{
			String: "user",
		},
		TiDBPasswordID: sql.NullString{
			String: "pass",
		},
		ClusterConnectionID: 1,
	})
	assert.NoError(suite.T(), err)

	err = suite.ComposeEntityDAO.DeleteIntegrationConfiguration(ctx, "AFAR")
	assert.NoError(suite.T(), err)

	var count int64
	err = globalDB.Model(&ClusterConnection{}).Count(&count).Error
	assert.NoError(suite.T(), err)

	assert.Equal(suite.T(), int64(0), count)

	err = globalDB.Model(&EnvironmentIdentifier{}).Count(&count).Error
	assert.NoError(suite.T(), err)
	assert.Equal(suite.T(), int64(0), count)
}

func TestEntitySuite(t *testing.T) {
	suite.Run(t, new(EntityTestSuite))
}
