package main

import (
	"database/sql"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/ec2metadata"
	"github.com/aws/aws-sdk-go/service/autoscaling"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/aws/aws-sdk-go/service/rds"

	serviceconfig "code.justin.tv/common/config"
	"code.justin.tv/common/twitchhttp"
	"code.justin.tv/d8a/buddy/cmd/buddy-service/api"
	"code.justin.tv/d8a/buddy/lib/awsutil"
	bulkschedule "code.justin.tv/d8a/buddy/lib/bulk"
	"code.justin.tv/d8a/buddy/lib/clusters/clusterdb"
	"code.justin.tv/d8a/buddy/lib/config"
	"code.justin.tv/d8a/buddy/lib/sandstorm"
	"code.justin.tv/d8a/buddy/lib/store"
	"code.justin.tv/d8a/iceman/lib/bulk"
	"code.justin.tv/d8a/iceman/lib/dbconf"
	"code.justin.tv/d8a/iceman/lib/queries"
	"code.justin.tv/d8a/iceman/lib/status"

	"fmt"
	"time"
)

func main() {
	serviceconfig.Register(map[string]string{
		"AwsRegion":  "us-west-2",
		"AwsProfile": "",
	})

	err := serviceconfig.Parse()
	if err != nil {
		log.Fatalln(err)
	}

	region := serviceconfig.MustResolve("AwsRegion")
	profile := serviceconfig.Resolve("AwsProfile")

	cache := bulkschedule.NewBulkStore()

	path := config.FindConfigFile()
	if path == "" {
		log.Fatalln("Could not locate `buddy.cfg` in the local directory or /etc/buddy.")
	}

	configFile, err := config.ReadConfig(path)
	if err != nil {
		log.Fatalln(err)
	}

	_, awsSession, err := awsutil.GetSession(region, profile, 8)
	if err != nil {
		log.Fatalln(fmt.Errorf(fmt.Sprintf("couldn't get an aws session with region: %s and profile: %s", region, profile)))
	}

	sandstormClient, err := sandstorm.New(configFile, region, configFile.SandstormRole, profile)
	if err != nil {
		log.Fatalln(err)
	}

	rdsClient := rds.New(awsSession)
	ec2Client := ec2.New(awsSession)
	autoscalingClient := autoscaling.New(awsSession)

	//If profile is not blank, that means we're on a non-amazon box & shouldn't try to get the local instance
	var localInstance *ec2.Instance
	var rdsInstance *rds.DBInstance
	if profile == "" {
		metadataClient := ec2metadata.New(awsSession)
		localInstance, err = awsutil.FetchLocalInstance(ec2Client, metadataClient)
		if err != nil {
			log.Fatalln(err)
		}

		rdsInstance, err = store.GetBuddyStore(rdsClient, ec2Client, autoscalingClient, localInstance, sandstormClient, configFile, false)
		if err != nil {
			log.Fatalln(err)
		}
	} else {
		instances, err := awsutil.FetchInstancesByIds(rdsClient, []*string{aws.String(configFile.StoreIdentifier)})
		if err != nil {
			log.Fatalln(err)
		}
		if len(instances) < 1 {
			log.Fatalln("Could not find a datastore by identifier", configFile.StoreIdentifier)
		}
		rdsInstance = instances[0]
	}

	storeDb, err := store.OpenDbConn(rdsInstance, sandstormClient)
	if err != nil {
		log.Fatalln(err)
	}

	schedule := bulkschedule.NewSchedule(configFile.BulkSchedule)
	stopBulkOpsFlag := configFile.StopBulkOps
	go func() {
		for {
			clusterList, err := store.GetClusters(storeDb)
			if err != nil {
				log.Println("Error retrieving clusters: ", err)
			}

			configFile.Cluster = clusterList

			nextBulk := schedule.NextRun(time.Now())
			if !nextBulk.IsZero() && !stopBulkOpsFlag {
				//Run a bulk run & start over
				time.Sleep(nextBulk.Sub(time.Now()))
				err = executeBulkRun(storeDb, cache, configFile)
				if err != nil {
					log.Println("Error running bulk: ", err)
				}
			} else {
				//Don't grind in a tight loop
				time.Sleep(time.Minute)
			}
		}
	}()

	go func() {
		failedConns := 0
		for {
			err := testStoreConn(rdsInstance, sandstormClient)
			if err != nil {
				log.Println("Store connection test failed: ", err)
				failedConns++
				if failedConns >= 3 {
					log.Println("Resetting super user password for store.")
					err := store.RefreshSuperUserPassword(rdsClient, rdsInstance, sandstormClient)
					if err != nil {
						log.Println("Could not fix superuser password: ", err)
					}

					time.Sleep(time.Second * 60)
				}
			} else {
				failedConns = 0
			}

			time.Sleep(time.Second * 5)
		}
	}()

	server, err := api.NewServer(configFile, schedule, cache, storeDb, sandstormClient)
	if err != nil {
		log.Fatalln(err)
	}

	log.Fatal(twitchhttp.ListenAndServe(server))
}

func testStoreConn(rdsInstance *rds.DBInstance, sandstormClient sandstorm.SandstormAPI) error {
	conn, err := store.OpenDbConn(rdsInstance, sandstormClient)
	if err != nil {
		return err
	}
	defer store.TryClose(conn)

	postgresQueries := clusterdb.PostgresQueries{}
	return postgresQueries.TestConnection(conn)
}

var lastClusterPull time.Time

func executeBulkRun(db *sql.DB, cache *bulkschedule.BulkRecordStore, configFile *config.ConfigFile) error {

	var err error
	if time.Now().Sub(lastClusterPull) > time.Minute {
		lastClusterPull = time.Now()
		configFile.Cluster, err = store.GetClusters(db)
		if err != nil {
			return err
		}
	}

	bulkRecord, err := cache.GetWorkableBulk(configFile)
	if err != nil {
		return err
	}
	if bulkRecord == nil {
		log.Println("No record to work")
		return nil
	}

	cluster := configFile.GetCluster(bulkRecord.Cluster)
	if cluster == nil {
		return fmt.Errorf("Could not find cluster %s\n", bulkRecord.Cluster)
	}

	migrationDbConf, err := cluster.GetIcemanDbConf("migrations")
	if err != nil {
		return fmt.Errorf("Could not build an iceman migrations dbconf for cluster %s: %v", bulkRecord.Cluster, err)
	}

	statuses, err := status.OrganizeStatuses(migrationDbConf)
	if err != nil {
		return fmt.Errorf("Error retrieving migration state for %s: %v", bulkRecord.Cluster, err)
	}

	for _, st := range statuses {
		if st.State != status.CompletedState {
			log.Println(st.Name)
			return fmt.Errorf("Unfinished Migrations for cluster %s", bulkRecord.Cluster)
		}
	}

	bulkDbConf, err := cluster.GetIcemanDbConf("bulk")
	if err != nil {
		return fmt.Errorf("Could not build an iceman dbconf for cluster %s: %v", bulkRecord.Cluster, err)
	}

	dbConn, err := dbconf.OpenDBFromDBConf(bulkDbConf)
	if err != nil {
		return err
	}
	defer queries.TryClose(dbConn)

	log.Printf("Running bulk against %s\n", bulkRecord.Cluster)

	return bulk.RunSingleBulk(dbConn, bulkDbConf.DriverQueries, &bulkRecord.BulkRecord)
}
