package awsutil

import (
	"strconv"

	"fmt"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/rds"
	"github.com/aws/aws-sdk-go/service/rds/rdsiface"
	multierror "github.com/hashicorp/go-multierror"
	"github.com/pkg/errors"
)

func InstanceTags(client rdsiface.RDSAPI) (ResourceTags, error) {
	outErr := new(multierror.Error)

	tagCollector := NewTagCollector(client)
	tagCollector.Start()

	params := rds.DescribeDBInstancesInput{} // get everythings
	err := client.DescribeDBInstancesPages(&params,
		func(page *rds.DescribeDBInstancesOutput, lastPage bool) bool {
			var resourcePage []*RdsResource
			for _, dbInstance := range page.DBInstances {
				resource := &RdsResource{
					Name: *dbInstance.DBInstanceIdentifier,
					Arn:  *dbInstance.DBInstanceArn,
					Data: make(map[string]string),
				}
				if dbInstance.ReadReplicaSourceDBInstanceIdentifier != nil {
					resource.Data["master-id"] = *dbInstance.ReadReplicaSourceDBInstanceIdentifier
				}
				if dbInstance.DBClusterIdentifier != nil {
					resource.Data["cluster-id"] = *dbInstance.DBClusterIdentifier
				}
				if dbInstance.MasterUsername == nil || dbInstance.Engine == nil || dbInstance.Endpoint == nil ||
					dbInstance.Endpoint.Address == nil || dbInstance.Endpoint.Port == nil {

					//Apparently you can just be missing stuff necessary to connect for whatever reason- usually it's some other team's
					//garbage instances and we should ignore, but complain so it's not a mystery about why scrape isn't picking up my instances
					fmt.Printf("Could not retrieve instance %s because some vital properties were missing.\n", *dbInstance.DBInstanceIdentifier)
					continue
				}

				if dbInstance.DBName != nil {
					resource.Data["dbname"] = *dbInstance.DBName
				}

				resource.Data["superuser"] = *dbInstance.MasterUsername
				resource.Data["driver"] = *dbInstance.Engine
				resource.Data["host"] = *dbInstance.Endpoint.Address
				resource.Data["port"] = strconv.Itoa(int(*dbInstance.Endpoint.Port))

				resourcePage = append(resourcePage, resource)
			}
			tagCollector.AddResourcePage(resourcePage)
			return true
		})
	if err != nil {
		outErr = multierror.Append(outErr, err)
	}

	err = tagCollector.End()
	if err != nil {
		outErr = multierror.Append(outErr, err)
	}

	return tagCollector.CollectedTags(), outErr.ErrorOrNil()
}

func ClusterTags(client rdsiface.RDSAPI) (ResourceTags, error) {
	outErr := new(multierror.Error)

	tagCollector := NewTagCollector(client)
	tagCollector.Start()

	params := rds.DescribeDBClustersInput{}
	records, err := client.DescribeDBClusters(&params)
	if err != nil {
		outErr = multierror.Append(outErr, err)
	}

	var resourcePage []*RdsResource
	for _, cluster := range records.DBClusters {
		resource := &RdsResource{
			Name: *cluster.DBClusterIdentifier,
			Arn:  *cluster.DBClusterArn,
			Data: make(map[string]string),
		}

		if cluster.ReplicationSourceIdentifier != nil {
			resource.Data["master-id"] = *cluster.ReplicationSourceIdentifier
		}

		if cluster.MasterUsername == nil || cluster.Engine == nil || cluster.Endpoint == nil || cluster.Port == nil {

			outErr = multierror.Append(outErr, fmt.Errorf("Could not retrieve cluster %s because some vital properties were missing", *cluster.DBClusterIdentifier))
			continue
		}

		if cluster.DatabaseName != nil {
			resource.Data["dbname"] = *cluster.DatabaseName
		}

		resource.Data["superuser"] = *cluster.MasterUsername

		var driver string
		switch *cluster.Engine {
		case "aurora":
			driver = "mysql"
		default:
			driver = *cluster.Engine
		}
		resource.Data["driver"] = driver
		resource.Data["host"] = *cluster.Endpoint
		resource.Data["port"] = strconv.Itoa(int(*cluster.Port))

		resourcePage = append(resourcePage, resource)
	}

	tagCollector.AddResourcePage(resourcePage)
	err = tagCollector.End()
	if err != nil {
		outErr = multierror.Append(outErr, err)
	}

	return tagCollector.CollectedTags(), outErr.ErrorOrNil()
}

func SubnetGroupTags(client rdsiface.RDSAPI) (ResourceTags, error) {
	outErr := new(multierror.Error)

	tagCollector := NewTagCollector(client)
	tagCollector.Start()

	params := rds.DescribeDBSubnetGroupsInput{} // get everythings
	err := client.DescribeDBSubnetGroupsPages(&params,
		func(page *rds.DescribeDBSubnetGroupsOutput, lastPage bool) bool {
			var resourcePage []*RdsResource
			for _, subnetGroup := range page.DBSubnetGroups {
				resourcePage = append(resourcePage, &RdsResource{
					Name: *subnetGroup.DBSubnetGroupName,
					Arn:  *subnetGroup.DBSubnetGroupArn,
					Data: make(map[string]string),
				})
			}
			tagCollector.AddResourcePage(resourcePage)
			return true
		})
	if err != nil {
		outErr = multierror.Append(outErr, errors.Wrap(err, "could not get subnet groups from aws sdk"))
	}
	err = tagCollector.End()
	if err != nil {
		outErr = multierror.Append(outErr, err)
	}

	return tagCollector.CollectedTags(), outErr.ErrorOrNil()
}

// FetchInstancesByIds is a method that retrieves aws API objects for instances that match the given identifiers
func FetchInstancesByIds(client rdsiface.RDSAPI, instanceIdentifiers []*string) ([]*rds.DBInstance, error) {
	var instances []*rds.DBInstance
	err := client.DescribeDBInstancesPages(&rds.DescribeDBInstancesInput{
		Filters: []*rds.Filter{
			&rds.Filter{
				Name:   aws.String("db-instance-id"),
				Values: instanceIdentifiers,
			},
		},
	}, func(page *rds.DescribeDBInstancesOutput, lastPage bool) bool {
		instances = append(instances, page.DBInstances...)
		return true
	})

	return instances, err
}

// FetchInstancesByClusters is a method that retrieves aws API objects for instances that belong to clusters specified
// by the given identifiers
func FetchInstancesByClusters(client rdsiface.RDSAPI, clusterIdentifiers []*string) ([]*rds.DBInstance, error) {
	var instances []*rds.DBInstance
	err := client.DescribeDBInstancesPages(&rds.DescribeDBInstancesInput{
		Filters: []*rds.Filter{
			&rds.Filter{
				Name:   aws.String("db-cluster-id"),
				Values: clusterIdentifiers,
			},
		},
	}, func(page *rds.DescribeDBInstancesOutput, lastPage bool) bool {
		instances = append(instances, page.DBInstances...)
		return true
	})

	return instances, err
}

// FetchClusters is a method that retireves aws API objects for clusters that match the given identifiers
func FetchClusters(client rdsiface.RDSAPI, clusterIdentifiers []*string) ([]*rds.DBCluster, error) {
	var clusters []*rds.DBCluster
	var marker *string

	for {
		resp, err := client.DescribeDBClusters(&rds.DescribeDBClustersInput{
			Filters: []*rds.Filter{
				&rds.Filter{
					Name:   aws.String("db-cluster-id"),
					Values: clusterIdentifiers,
				},
			},
			Marker: marker,
		})
		if err != nil {
			return clusters, err
		}

		clusters = append(clusters, resp.DBClusters...)

		if len(resp.DBClusters) == 0 || resp.Marker == nil {
			return clusters, nil
		}

		marker = resp.Marker
	}
}
