package store

import (
	"fmt"
	"time"

	"code.justin.tv/d8a/buddy/lib/sandstorm"
	"code.justin.tv/d8a/iceman/lib/migrations"
	"code.justin.tv/d8a/iceman/lib/queries"
	"code.justin.tv/d8a/iceman/lib/tables"

	"github.com/aws/aws-sdk-go/service/rds"
)

// SliceMigrations is an iceman Migrations provider that provides migrations from a slice in memory instead of
// the file system
type SliceMigrations struct {
	Migrations []*migrations.MigrationRecord
}

// FetchAllMigrations retrieves all stored iceman migrations
func (sm *SliceMigrations) FetchAllMigrations() ([]*migrations.MigrationRecord, error) {
	return sm.Migrations, nil
}

// FetchSingleMigration retrieves a single iceman migration by name
func (sm *SliceMigrations) FetchSingleMigration(name string) (*migrations.MigrationRecord, error) {
	for _, migration := range sm.Migrations {
		if migration.Name == name {
			return migration, nil
		}
	}

	return nil, nil
}

var jiggle time.Duration
var migrationSource *SliceMigrations

func init() {
	migrationSource = &SliceMigrations{
		Migrations: []*migrations.MigrationRecord{},
	}

	addMigration("AddClusterTable", 2017, time.March, 30,
		[]string{
			"CREATE TABLE IF NOT EXISTS clusters ( " +
				"id integer primary key NOT NULL, " +
				"name text, " +
				"driver text, " +
				"environment text, " +
				"migration_schema text, " +
				"migration_repository text " +
				");",
			"CREATE SEQUENCE clusters_id_seq " +
				"START WITH 1 " +
				"INCREMENT BY 1 " +
				"NO MINVALUE " +
				"NO MAXVALUE " +
				"CACHE 1;",
			`ALTER SEQUENCE clusters_id_seq OWNED BY clusters.id;`,
			`ALTER TABLE ONLY clusters ALTER COLUMN id SET DEFAULT nextval('clusters_id_seq'::regclass);`,
			`CREATE UNIQUE INDEX idx_clusters_id ON clusters USING btree (id);`,
		},
		[]string{
			`DROP TABLE IF EXISTS clusters;`,
		})
	addMigration("AddAlarmPriorityToClusterTable", 2017, time.April, 4,
		[]string{
			`ALTER TABLE clusters ADD COLUMN alarm_priority integer;`,
		},
		[]string{
			`ALTER TABLE clusters DROP COLUMN alarm_priority;`,
		})
	addMigration("MakeClusterColumnsNotNull", 2017, time.April, 12,
		[]string{
			"ALTER TABLE clusters ALTER COLUMN name SET NOT NULL;",
			"ALTER TABLE clusters ALTER COLUMN driver SET NOT NULL;",
			"ALTER TABLE clusters ALTER COLUMN environment SET NOT NULL;",
			"ALTER TABLE clusters ALTER COLUMN migration_schema SET NOT NULL;",
			"ALTER TABLE clusters ALTER COLUMN migration_repository SET NOT NULL;",
		},
		[]string{
			"ALTER TABLE clusters ALTER COLUMN name SET NULL;",
			"ALTER TABLE clusters ALTER COLUMN driver SET NULL;",
			"ALTER TABLE clusters ALTER COLUMN environment SET NULL;",
			"ALTER TABLE clusters ALTER COLUMN migration_schema SET NULL;",
			"ALTER TABLE clusters ALTER COLUMN migration_repository SET NULL;",
		})
	addMigration("AddConnectionInfoToCluster", 2017, time.April, 12,
		[]string{
			"ALTER TABLE clusters ADD COLUMN endpoint_url text NOT NULL;",
			"ALTER TABLE clusters ADD COLUMN endpoint_port int NOT NULL;",
			"ALTER TABLE clusters ADD COLUMN database text NOT NULL;",
			"ALTER TABLE clusters ADD COLUMN superuser text NOT NULL;",
		},
		[]string{
			"ALTER TABLE clusters DROP COLUMN endpoint_url;",
			"ALTER TABLE clusters DROP COLUMN endpoint_port;",
			"ALTER TABLE clusters DROP COLUMN database text;",
			"ALTER TABLE clusters DROP COLUMN superuser text;",
		})
	addMigration("AddUserTable", 2017, time.April, 12,
		[]string{
			"CREATE TABLE IF NOT EXISTS users ( " +
				"id integer primary key NOT NULL, " +
				"name text NOT NULL, " +
				"secret text NOT NULL, " +
				"cluster_id integer NOT NULL REFERENCES clusters ON DELETE CASCADE " +
				");",
			"CREATE SEQUENCE users_id_seq " +
				"START WITH 1 " +
				"INCREMENT BY 1 " +
				"NO MINVALUE " +
				"NO MAXVALUE " +
				"CACHE 1;",
			"ALTER SEQUENCE users_id_seq OWNED BY users.id;",
			"ALTER TABLE ONLY users ALTER COLUMN id SET DEFAULT nextval('users_id_seq'::regclass);",
			"CREATE UNIQUE INDEX idx_users_id ON users USING btree (id);",
			"CREATE INDEX idx_users_by_cluster ON users USING btree (cluster_id);",
		},
		[]string{
			"DROP TABLE IF EXISTS users;",
		})
	addMigration("AddRootIdentifier", 2017, time.April, 28,
		[]string{
			"ALTER TABLE clusters ADD COLUMN is_aurora bool NOT NULL DEFAULT false",
			"ALTER TABLE clusters ADD COLUMN root_identifier text NOT NULL DEFAULT ''",
			"ALTER TABLE clusters ALTER COLUMN root_identifier DROP DEFAULT",
		}, []string{
			"ALTER TABLE clusters DROP COLUMN is_aurora",
			"ALTER TABLE clusters DROP COLUMN root_identifier",
		})
	addMigration("AddRolePairs", 2017, time.May, 26,
		[]string{
			"CREATE TABLE IF NOT EXISTS role_pairs ( " +
				"id integer primary key NOT NULL, " +
				"name text NOT NULL, " +
				"cluster_id integer NOT NULL REFERENCES clusters ON DELETE CASCADE " +
				");",
			"CREATE SEQUENCE role_pairs_id_seq " +
				"START WITH 1 " +
				"INCREMENT BY 1 " +
				"NO MINVALUE " +
				"NO MAXVALUE " +
				"CACHE 1;",
			"ALTER SEQUENCE role_pairs_id_seq OWNED BY role_pairs.id;",
			"ALTER TABLE ONLY role_pairs ALTER COLUMN id SET DEFAULT nextval('role_pairs_id_seq'::regclass);",
			"CREATE UNIQUE INDEX idx_role_pairs_id ON role_pairs USING btree (id);",
			"CREATE INDEX idx_role_pairs_by_cluster ON role_pairs USING btree (cluster_id);",
		}, []string{
			"DROP TABLE IF EXISTS role_pairs;",
		})
	addMigration("AddPairMembership", 2017, time.May, 26,
		[]string{
			"ALTER TABLE users ADD COLUMN role_pair_id integer NULL REFERENCES role_pairs ON DELETE SET NULL",
		}, []string{
			"ALTER TABLE users DROP COLUMN role_pair_id",
		})
	addMigration("AddServices", 2017, time.June, 6,
		[]string{
			"CREATE TABLE IF NOT EXISTS role_services ( " +
				"id integer primary key NOT NULL, " +
				"name text NOT NULL, " +
				"username_secret text NOT NULL, " +
				"password_secret text NOT NULL, " +
				"role_pair_id integer NOT NULL REFERENCES role_pairs ON DELETE CASCADE " +
				")",
			"CREATE SEQUENCE role_services_id_seq " +
				"START WITH 1 " +
				"INCREMENT BY 1 " +
				"NO MINVALUE " +
				"NO MAXVALUE " +
				"CACHE 1",
			"ALTER SEQUENCE role_services_id_seq OWNED BY role_services.id",
			"ALTER TABLE ONLY role_services ALTER COLUMN id SET DEFAULT nextval('role_services_id_seq'::regclass)",
			"CREATE INDEX idx_role_services_by_role_pair ON role_services USING btree (role_pair_id)",
		}, []string{})
}

func addMigration(name string, year int, month time.Month, day int, up, down []string) {
	migrationText := "up:\n  operations:\n    -\n      queries:"
	for _, query := range up {
		migrationText = fmt.Sprintf("%s\n        - %s", migrationText, query)
	}
	migrationText = fmt.Sprintf("%s\n      timeout: 5000\n      txn: true\n  timeout: 5000\n", migrationText)
	migrationText = fmt.Sprintf("%s\ndown:\n  operations:\n    -\n      queries:", migrationText)

	for _, query := range down {
		migrationText = fmt.Sprintf("%s\n        - %s", migrationText, query)
	}
	migrationText = fmt.Sprintf("%s\n      timeout: 5000\n      txn: true\n  timeout: 5000\n", migrationText)

	migrationSource.Migrations = append(migrationSource.Migrations, &migrations.MigrationRecord{
		Name:      name,
		Filename:  name,
		CreatedAt: time.Date(year, month, day, 0, 0, 0, 0, time.UTC).Add(jiggle),
		Content:   []byte(migrationText),
	})

	jiggle += time.Second
}

// MigrateStore applies the above iceman migrations to the buddy store
func MigrateStore(rdsInstance *rds.DBInstance, sandstormClient sandstorm.SandstormAPI) error {
	icemanConn, err := OpenDbConn(rdsInstance, sandstormClient)
	if err != nil {
		return err
	}

	query := &queries.PostgresQueries{}
	err = tables.VerifyIcemanTables(icemanConn, query)
	if err != nil {
		return err
	}

	_, err = migrations.RunMigrations(icemanConn, query, migrationSource, true)
	return err
}
