package gorm_test

import (
	"fmt"
	"os"
	"testing"
	"time"

	"github.com/porter-dev/porter/api/server/shared/config/env"
	"github.com/porter-dev/porter/api/types"
	"github.com/porter-dev/porter/internal/adapter"
	"github.com/porter-dev/porter/internal/models"
	ints "github.com/porter-dev/porter/internal/models/integrations"
	"github.com/porter-dev/porter/internal/repository"
	"github.com/porter-dev/porter/internal/repository/gorm"

	_gorm "gorm.io/gorm"
)

type tester struct {
	repo           repository.Repository
	key            *[32]byte
	dbFileName     string
	db             *_gorm.DB
	initUsers      []*models.User
	initProjects   []*models.Project
	initGRs        []*models.GitRepo
	initRegs       []*models.Registry
	initClusters   []*models.Cluster
	initHRs        []*models.HelmRepo
	initInfras     []*models.Infra
	initReleases   []*models.Release
	initInvites    []*models.Invite
	initKubeEvents []*models.KubeEvent
	initCCs        []*models.ClusterCandidate
	initKIs        []*ints.KubeIntegration
	initBasics     []*ints.BasicIntegration
	initOIDCs      []*ints.OIDCIntegration
	initOAuths     []*ints.OAuthIntegration
	initGCPs       []*ints.GCPIntegration
	initAWSs       []*ints.AWSIntegration
	initAllowlist  []*models.Allowlist
	initTags       []*models.Tag
	initAPITokens  []*models.APIToken
}

func setupTestEnv(tester *tester, t *testing.T) {
	t.Helper()

	db, err := adapter.New(&env.DBConf{
		EncryptionKey: "__random_strong_encryption_key__",
		SQLLite:       true,
		SQLLitePath:   tester.dbFileName,
	})
	if err != nil {
		t.Fatalf("%v\n", err)
	}

	err = db.AutoMigrate(
		&models.Project{},
		&models.Role{},
		&models.User{},
		&models.Session{},
		&models.GitRepo{},
		&models.Registry{},
		&models.Release{},
		&models.Environment{},
		&models.Deployment{},
		&models.HelmRepo{},
		&models.Cluster{},
		&models.ClusterCandidate{},
		&models.ClusterResolver{},
		&models.Infra{},
		&models.Operation{},
		&models.GitActionConfig{},
		&models.Invite{},
		&models.KubeEvent{},
		&models.KubeSubEvent{},
		&models.Onboarding{},
		&models.Allowlist{},
		&models.Tag{},
		&models.APIToken{},
		&ints.KubeIntegration{},
		&ints.BasicIntegration{},
		&ints.OIDCIntegration{},
		&ints.OAuthIntegration{},
		&ints.GCPIntegration{},
		&ints.AWSIntegration{},
		&ints.ClusterTokenCache{},
		&ints.RegTokenCache{},
		&ints.HelmRepoTokenCache{},
		&ints.GithubAppInstallation{},
	)

	if err != nil {
		t.Fatalf("%v\n", err)
	}

	var key [32]byte

	for i, b := range []byte("__random_strong_encryption_key__") {
		key[i] = b
	}

	tester.key = &key
	tester.db = db
	tester.repo = gorm.NewRepository(db, &key, nil)
}

func cleanup(tester *tester, t *testing.T) {
	t.Helper()

	// remove the created file file
	os.Remove(tester.dbFileName)
}

func initUser(tester *tester, t *testing.T) {
	t.Helper()

	user := &models.User{
		Email:    "example@example.com",
		Password: "hello1234",
	}

	user, err := tester.repo.User().CreateUser(user)
	if err != nil {
		t.Fatalf("%v\n", err)
	}

	tester.initUsers = append(tester.initUsers, user)
}

func initAllowlist(tester *tester, t *testing.T) {
	t.Helper()

	allowedUser := &models.Allowlist{
		UserEmail: "some@email.com",
	}

	tester.db.Create(&allowedUser)

	tester.initAllowlist = append(tester.initAllowlist, allowedUser)
}

func initMultiUser(tester *tester, t *testing.T) {
	t.Helper()

	user := &models.User{
		Email:    "example@example.com",
		Password: "hello1234",
	}

	user, err := tester.repo.User().CreateUser(user)
	if err != nil {
		t.Fatalf("%v\n", err)
	}

	tester.initUsers = append(tester.initUsers, user)

	user = &models.User{
		Email:    "example2@example.com",
		Password: "hello1234",
	}

	user, err = tester.repo.User().CreateUser(user)

	if err != nil {
		t.Fatalf("%v\n", err)
	}

	tester.initUsers = append(tester.initUsers, user)
}

func initProject(tester *tester, t *testing.T) {
	t.Helper()

	proj := &models.Project{
		Name: "project-test",
	}

	proj, err := tester.repo.Project().CreateProject(proj)
	if err != nil {
		t.Fatalf("%v\n", err)
	}

	tester.initProjects = append(tester.initProjects, proj)
}

func initProjectRole(tester *tester, t *testing.T) {
	t.Helper()

	role := &models.Role{
		Role: types.Role{
			Kind:      types.RoleAdmin,
			UserID:    tester.initUsers[0].Model.ID,
			ProjectID: tester.initProjects[0].Model.ID,
		},
	}

	role, err := tester.repo.Project().CreateProjectRole(tester.initProjects[0], role)
	if err != nil {
		t.Fatalf("%v\n", err)
	}
}

func initKubeIntegration(tester *tester, t *testing.T) {
	t.Helper()

	if len(tester.initProjects) == 0 {
		initProject(tester, t)
	}

	if len(tester.initUsers) == 0 {
		initUser(tester, t)
	}

	ki := &ints.KubeIntegration{
		Mechanism:  ints.KubeLocal,
		ProjectID:  tester.initProjects[0].ID,
		UserID:     tester.initUsers[0].ID,
		Kubeconfig: []byte("current-context: testing\n"),
	}

	ki, err := tester.repo.KubeIntegration().CreateKubeIntegration(ki)
	if err != nil {
		t.Fatalf("%v\n", err)
	}

	tester.initKIs = append(tester.initKIs, ki)
}

func initBasicIntegration(tester *tester, t *testing.T) {
	t.Helper()

	if len(tester.initProjects) == 0 {
		initProject(tester, t)
	}

	if len(tester.initUsers) == 0 {
		initUser(tester, t)
	}

	basic := &ints.BasicIntegration{
		ProjectID: tester.initProjects[0].ID,
		UserID:    tester.initUsers[0].ID,
		Username:  []byte("username"),
		Password:  []byte("password"),
	}

	basic, err := tester.repo.BasicIntegration().CreateBasicIntegration(basic)
	if err != nil {
		t.Fatalf("%v\n", err)
	}

	tester.initBasics = append(tester.initBasics, basic)
}

func initOIDCIntegration(tester *tester, t *testing.T) {
	t.Helper()

	if len(tester.initProjects) == 0 {
		initProject(tester, t)
	}

	if len(tester.initUsers) == 0 {
		initUser(tester, t)
	}

	oidc := &ints.OIDCIntegration{
		Client:       ints.OIDCKube,
		ProjectID:    tester.initProjects[0].ID,
		UserID:       tester.initUsers[0].ID,
		IssuerURL:    []byte("https://oidc.example.com"),
		ClientID:     []byte("exampleclientid"),
		ClientSecret: []byte("exampleclientsecret"),
		IDToken:      []byte("idtoken"),
		RefreshToken: []byte("refreshtoken"),
	}

	oidc, err := tester.repo.OIDCIntegration().CreateOIDCIntegration(oidc)
	if err != nil {
		t.Fatalf("%v\n", err)
	}

	tester.initOIDCs = append(tester.initOIDCs, oidc)
}

func initOAuthIntegration(tester *tester, t *testing.T) {
	t.Helper()

	if len(tester.initProjects) == 0 {
		initProject(tester, t)
	}

	if len(tester.initUsers) == 0 {
		initUser(tester, t)
	}

	oauth := &ints.OAuthIntegration{
		SharedOAuthModel: ints.SharedOAuthModel{
			ClientID:     []byte("exampleclientid"),
			AccessToken:  []byte("idtoken"),
			RefreshToken: []byte("refreshtoken"),
		},
		Client:    types.OAuthGithub,
		ProjectID: tester.initProjects[0].ID,
		UserID:    tester.initUsers[0].ID,
	}

	oauth, err := tester.repo.OAuthIntegration().CreateOAuthIntegration(oauth)
	if err != nil {
		t.Fatalf("%v\n", err)
	}

	tester.initOAuths = append(tester.initOAuths, oauth)
}

func initGCPIntegration(tester *tester, t *testing.T) {
	t.Helper()

	if len(tester.initProjects) == 0 {
		initProject(tester, t)
	}

	if len(tester.initUsers) == 0 {
		initUser(tester, t)
	}

	gcp := &ints.GCPIntegration{
		ProjectID:    tester.initProjects[0].ID,
		UserID:       tester.initUsers[0].ID,
		GCPProjectID: "test-proj-123456",
		GCPUserEmail: "test@test.it",
		GCPKeyData:   []byte("{\"test\":\"key\"}"),
	}

	gcp, err := tester.repo.GCPIntegration().CreateGCPIntegration(gcp)
	if err != nil {
		t.Fatalf("%v\n", err)
	}

	tester.initGCPs = append(tester.initGCPs, gcp)
}

func initAWSIntegration(tester *tester, t *testing.T) {
	t.Helper()

	if len(tester.initProjects) == 0 {
		initProject(tester, t)
	}

	if len(tester.initUsers) == 0 {
		initUser(tester, t)
	}

	aws := &ints.AWSIntegration{
		ProjectID:          tester.initProjects[0].ID,
		UserID:             tester.initUsers[0].ID,
		AWSClusterID:       []byte("example-cluster-0"),
		AWSAccessKeyID:     []byte("accesskey"),
		AWSSecretAccessKey: []byte("secret"),
		AWSSessionToken:    []byte("optional"),
	}

	aws, err := tester.repo.AWSIntegration().CreateAWSIntegration(aws)
	if err != nil {
		t.Fatalf("%v\n", err)
	}

	tester.initAWSs = append(tester.initAWSs, aws)
}

func initClusterCandidate(tester *tester, t *testing.T) {
	t.Helper()

	if len(tester.initProjects) == 0 {
		initProject(tester, t)
	}

	cc := &models.ClusterCandidate{
		AuthMechanism:     models.AWS,
		ProjectID:         tester.initProjects[0].ID,
		CreatedClusterID:  0,
		Resolvers:         []models.ClusterResolver{},
		Name:              "cluster-test",
		Server:            "https://localhost",
		ContextName:       "context-test",
		AWSClusterIDGuess: []byte("example-cluster-0"),
		Kubeconfig:        []byte("current-context: testing\n"),
	}

	cc, err := tester.repo.Cluster().CreateClusterCandidate(cc)
	if err != nil {
		t.Fatalf("%v\n", err)
	}

	tester.initCCs = append(tester.initCCs, cc)
}

func initCluster(tester *tester, t *testing.T) {
	t.Helper()

	if len(tester.initProjects) == 0 {
		initProject(tester, t)
	}

	if len(tester.initKIs) == 0 {
		initKubeIntegration(tester, t)
	}

	cluster := &models.Cluster{
		ProjectID:                tester.initProjects[0].ID,
		Name:                     "cluster-test",
		Server:                   "https://localhost",
		KubeIntegrationID:        tester.initKIs[0].ID,
		CertificateAuthorityData: []byte("-----BEGIN"),
	}

	cluster, err := tester.repo.Cluster().CreateCluster(cluster)
	if err != nil {
		t.Fatalf("%v\n", err)
	}

	tester.initClusters = append(tester.initClusters, cluster)
}

func initGitRepo(tester *tester, t *testing.T) {
	t.Helper()

	if len(tester.initProjects) == 0 {
		initProject(tester, t)
	}

	if len(tester.initOAuths) == 0 {
		initOAuthIntegration(tester, t)
	}

	gr := &models.GitRepo{
		ProjectID:          tester.initProjects[0].ID,
		RepoEntity:         "porter-dev",
		OAuthIntegrationID: tester.initOAuths[0].ID,
	}

	gr, err := tester.repo.GitRepo().CreateGitRepo(gr)
	if err != nil {
		t.Fatalf("%v\n", err)
	}

	tester.initGRs = append(tester.initGRs, gr)
}

func initRegistry(tester *tester, t *testing.T) {
	t.Helper()

	if len(tester.initProjects) == 0 {
		initProject(tester, t)
	}

	reg := &models.Registry{
		ProjectID: tester.initProjects[0].ID,
		Name:      "registry-test",
	}

	reg, err := tester.repo.Registry().CreateRegistry(reg)
	if err != nil {
		t.Fatalf("%v\n", err)
	}

	tester.initRegs = append(tester.initRegs, reg)
}

func initHelmRepo(tester *tester, t *testing.T) {
	t.Helper()

	if len(tester.initProjects) == 0 {
		initProject(tester, t)
	}

	hr := &models.HelmRepo{
		Name:      "helm-repo-test",
		RepoURL:   "https://example-repo.com",
		ProjectID: tester.initProjects[0].Model.ID,
	}

	hr, err := tester.repo.HelmRepo().CreateHelmRepo(hr)
	if err != nil {
		t.Fatalf("%v\n", err)
	}

	tester.initHRs = append(tester.initHRs, hr)
}

func initInfra(tester *tester, t *testing.T) {
	t.Helper()

	if len(tester.initProjects) == 0 {
		initProject(tester, t)
	}

	infra := &models.Infra{
		Kind:      types.InfraECR,
		ProjectID: tester.initProjects[0].Model.ID,
		Status:    types.StatusCreated,
	}

	infra, err := tester.repo.Infra().CreateInfra(infra)
	if err != nil {
		t.Fatalf("%v\n", err)
	}

	tester.initInfras = append(tester.initInfras, infra)
}

func initInvite(tester *tester, t *testing.T) {
	t.Helper()

	if len(tester.initProjects) == 0 {
		initProject(tester, t)
	}

	expiry := time.Now().Add(24 * time.Hour)

	invite := &models.Invite{
		Token:     "abcd",
		Expiry:    &expiry,
		Email:     "testing@test.it",
		ProjectID: 1,
	}

	invite, err := tester.repo.Invite().CreateInvite(invite)
	if err != nil {
		t.Fatalf("%v\n", err)
	}

	tester.initInvites = append(tester.initInvites, invite)
}

func initRelease(tester *tester, t *testing.T) {
	t.Helper()

	if len(tester.initProjects) == 0 {
		initProject(tester, t)
	}

	release := &models.Release{
		Name:         "denver-meister-dakota",
		Namespace:    "default",
		ProjectID:    1,
		ClusterID:    1,
		WebhookToken: "abcdefgh",
	}

	release, err := tester.repo.Release().CreateRelease(release)
	if err != nil {
		t.Fatalf("%v\n", err)
	}

	tester.initReleases = append(tester.initReleases, release)
}

func initKubeEvents(tester *tester, t *testing.T) {
	t.Helper()

	if len(tester.initProjects) == 0 {
		initProject(tester, t)
	}

	initEvents := make([]*models.KubeEvent, 0)

	// init 100 events for testing purposes
	for i := 0; i < 100; i++ {
		refType := "pod"

		if i >= 50 {
			refType = "node"
		}

		event := &models.KubeEvent{
			ProjectID:    tester.initProjects[0].Model.ID,
			ClusterID:    tester.initClusters[0].Model.ID,
			Name:         fmt.Sprintf("%s-example-%d", refType, i),
			Namespace:    "default",
			ResourceType: refType,
		}

		event, err := tester.repo.KubeEvent().CreateEvent(event)
		if err != nil {
			t.Fatalf("%v\n", err)
		}

		// append a sub event as well
		subEvent := &models.KubeSubEvent{
			EventType: "pod",
			Message:   "Pod killed",
			Reason:    "OOM: memory limit exceeded",
		}

		err = tester.repo.KubeEvent().AppendSubEvent(event, subEvent)

		if err != nil {
			t.Fatalf("%v\n", err)
		}

		initEvents = append(initEvents, event)
	}

	for i := 99; i >= 0; i-- {
		subEvent := &models.KubeSubEvent{
			EventType: "pod",
			Message:   "Pod killed",
			Reason:    "OOM: memory limit exceeded",
		}

		err := tester.repo.KubeEvent().AppendSubEvent(initEvents[i], subEvent)
		if err != nil {
			t.Fatalf("%v\n", err)
		}
	}

	tester.initKubeEvents = initEvents
}

func initAPITokens(tester *tester, t *testing.T) {
	t.Helper()

	ti := time.Now().Add(10 * time.Minute)
	tokens := &models.APIToken{
		UniqueID:        "1",
		ProjectID:       tester.initProjects[0].Model.ID,
		CreatedByUserID: 1,
		Expiry:          &ti,
		Revoked:         false,
		Name:            "test-key",
	}

	tester.db.Create(&tokens)

	tester.initAPITokens = append(tester.initAPITokens, tokens)
}
