package db

import (
	"database/sql"

	sq "github.com/Masterminds/squirrel"
	"github.com/go-sql-driver/mysql"
)

func (db *realDB) InsertClusterComponent(cluster_type, cluster_id, component_name, version, cloud_provider, instance, overwrite_image_tag, base_image, image_tag string, update_strategy int) error {
	_, err := db.Exec("INSERT INTO cluster_components (cluster_type, cluster_id, component_name, version, cloud_provider, instance, overwrite_image_tag, base_image, image_tag, update_strategy) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
		cluster_type, cluster_id, component_name, version, cloud_provider, instance, overwrite_image_tag, base_image, image_tag, update_strategy)
	if e, ok := err.(*mysql.MySQLError); ok && e.Number == 1062 {
		return ErrAlreadyExists
	}
	return err
}

func (db *realDB) GetClusterComponent(clusterType, clusterID, componentName string) (ClusterComponentRow, error) {
	component := ClusterComponentRow{}
	err := db.Get(&component, "SELECT * FROM cluster_components WHERE cluster_type = ? AND cluster_id = ? AND component_name = ?", clusterType, clusterID, componentName)
	if err == sql.ErrNoRows {
		return component, ErrNotFound
	}

	return component, err
}

func (db *realDB) ListClusterComponents(clusterType, clusterID string) ([]ClusterComponentRow, error) {
	components := []ClusterComponentRow{}
	err := db.Select(&components, "SELECT * FROM cluster_components WHERE cluster_type = ? AND cluster_id = ? AND delete_time IS NULL", clusterType, clusterID)
	return components, err
}

func clusterComponentSearchConds(clusterType, componentName string, clusterIDs []string, componentVersion, imageTag string, showDeleted bool) sq.Eq {
	conds := sq.Eq{
		"cluster_type": clusterType,
	}
	if !showDeleted {
		conds["delete_time"] = nil
	}
	if componentName != "" {
		conds["component_name"] = componentName
	}
	if len(clusterIDs) > 0 {
		conds["cluster_id"] = clusterIDs
	}
	if componentVersion != "" {
		conds["version"] = componentVersion
	}
	if imageTag != "" {
		conds["image_tag"] = imageTag
	}
	return conds
}

func (db *realDB) SearchClusterComponents(clusterType, componentName string, clusterIDs []string, componentVersion, imageTag string, showDeleted bool, pageSize, page int32) ([]ClusterComponentRow, error) {
	if pageSize <= 0 {
		pageSize = 100
	}
	if page <= 0 {
		page = 1
	}

	components := []ClusterComponentRow{}
	sql, args, err := sq.Select("*").From("cluster_components").
		Where(clusterComponentSearchConds(clusterType, componentName, clusterIDs, componentVersion, imageTag, showDeleted)).
		OrderBy("cluster_id ASC", "component_name ASC", "version ASC", "id ASC").
		Limit(uint64(pageSize)).
		Offset(uint64(pageSize) * uint64(page-1)).
		ToSql()
	if err != nil {
		return nil, err
	}
	err = db.Select(&components, sql, args...)
	return components, err
}

func (db *realDB) CountClusterComponents(clusterType, componentName string, clusterIDs []string, componentVersion, imageTag string, showDeleted bool) (int64, error) {
	var total int64
	sql, args, err := sq.Select("COUNT(*)").From("cluster_components").
		Where(clusterComponentSearchConds(clusterType, componentName, clusterIDs, componentVersion, imageTag, showDeleted)).
		ToSql()
	if err != nil {
		return 0, err
	}
	err = db.Get(&total, sql, args...)
	return total, err
}

func (db *realDB) UpdateClusterComponent(cluster_type, cluster_id, component_name, version, cloud_provider, instance, overwrite_image_tag, base_image, image_tag string, update_strategy int) error {
	_, err := db.Exec("UPDATE cluster_components SET version = ?, cloud_provider = ?, instance = ?, overwrite_image_tag = ?, base_image = ?, image_tag = ?, update_strategy = ? WHERE cluster_type = ? AND cluster_id = ? AND component_name = ?", version, cloud_provider, instance, overwrite_image_tag, base_image, image_tag, update_strategy, cluster_type, cluster_id, component_name)
	if err == sql.ErrNoRows {
		return ErrNotFound
	}
	return err
}

func (db *realDB) DeleteClusterComponentsByCluster(clusterType, clusterID string) (int64, error) {
	res, err := db.Exec("UPDATE cluster_components SET delete_time = CURRENT_TIMESTAMP(6) WHERE cluster_type = ? AND cluster_id = ? AND delete_time IS NULL", clusterType, clusterID)
	if err != nil {
		return 0, err
	}
	return res.RowsAffected()
}

func (db *realDB) InsertDefaultVersion(clusterType, component, clusterVersion, cloudProvider, region string, teanantID uint64, version string) error {
	_, err := db.Exec("INSERT INTO default_versions (cluster_type, component_name, cluster_version, cloud_provider, region, tenant_id, version) VALUES (?, ?, ?, ?, ?, ?, ?)", clusterType, component, clusterVersion, cloudProvider, region, teanantID, version)
	if e, ok := err.(*mysql.MySQLError); ok && e.Number == 1062 {
		return ErrAlreadyExists
	}
	return err
}

func (db *realDB) GetDefaultVersion(clusterType, component, clusterVersion, cloudProvider, region string, teanantID uint64) (string, error) {
	conds := sq.Eq{
		"cluster_type":    clusterType,
		"component_name":  component,
		"cluster_version": clusterVersion,
		"cloud_provider":  cloudProvider,
		"region":          region,
		"tenant_id":       teanantID,
	}
	sqls, args, err := sq.Select("version").From("default_versions").Where(conds).ToSql()
	if err != nil {
		return "", err
	}

	var row DefaultVersionRow
	err = db.Get(&row, sqls, args...)
	if err == sql.ErrNoRows {
		return "", ErrNotFound
	}
	return row.Version, err
}

func (db *realDB) ListDefaultVersionRules(clusterType string, component *string, tenantID *uint64) ([]DefaultVersionRow, error) {
	conds := sq.Eq{
		"cluster_type": clusterType,
	}
	if component != nil && *component != "" {
		conds["component_name"] = *component
	}
	if tenantID != nil {
		conds["tenant_id"] = []uint64{*tenantID, 0}
	}

	sql, args, err := sq.Select("*").From("default_versions").Where(conds).
		ToSql()
	if err != nil {
		return nil, err
	}

	var rules []DefaultVersionRow
	err = db.Select(&rules, sql, args...)
	return rules, err
}

func (db *realDB) UpdateDefaultVersion(clusterType, component, clusterVersion, cloudProvider, region string, teanantID uint64, version string) error {
	conds := sq.Eq{
		"cluster_type":    clusterType,
		"component_name":  component,
		"cluster_version": clusterVersion,
		"cloud_provider":  cloudProvider,
		"region":          region,
		"tenant_id":       teanantID,
	}
	sqls, args, err := sq.Update("default_versions").Set("version", version).Where(conds).ToSql()
	if err != nil {
		return err
	}

	_, err = db.Exec(sqls, args...)
	return err
}

func (db *realDB) DeleteDefaultVersion(clusterType, component, clusterVersion, cloudProvider, region string, teanantID uint64) error {
	conds := sq.Eq{
		"cluster_type":    clusterType,
		"component_name":  component,
		"cluster_version": clusterVersion,
		"cloud_provider":  cloudProvider,
		"region":          region,
		"tenant_id":       teanantID,
	}
	sqls, args, err := sq.Delete("default_versions").Where(conds).ToSql()
	if err != nil {
		return err
	}
	_, err = db.Exec(sqls, args...)
	// todo: what is the error when not exist
	return err
}

func (db *realDB) InsertComponentImage(clusterType, component, version, baseImage, tag string, tenantID uint64) error {
	var err error
	_, err = db.Exec("INSERT INTO component_images (cluster_type, component_name, version, tenant_id, base_image, tag) VALUES (?, ?, ?, ?, ?, ?)", clusterType, component, version, tenantID, baseImage, tag)
	if e, ok := err.(*mysql.MySQLError); ok && e.Number == 1062 {
		return ErrAlreadyExists
	}
	return err
}

func (db *realDB) UpsertComponentImage(clusterType, component, version, baseImage, tag string, tenantID uint64) error {
	_, err := db.Exec(
		"INSERT INTO component_images (cluster_type, component_name, version, tenant_id, base_image, tag) VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE base_image = VALUES(base_image), tag = VALUES(tag)",
		clusterType, component, version, tenantID, baseImage, tag,
	)
	return err
}

func (db *realDB) GetComponentImage(clusterType, component, version string, tenantID uint64) (string, string, error) {
	var row ComponentImageRow
	baseQuery := "SELECT base_image, tag FROM component_images WHERE cluster_type = ? AND component_name = ? AND version = ? AND tenant_id = ?"
	args := []interface{}{clusterType, component, version, tenantID}
	err := db.Get(&row, baseQuery, args...)
	if err == sql.ErrNoRows && tenantID != 0 {
		// try fallback to global
		args = []interface{}{clusterType, component, version, uint64(0)}
		err = db.Get(&row, baseQuery, args...)
	}
	if err == sql.ErrNoRows {
		return "", "", ErrNotFound
	}
	return row.BaseImage, row.Tag, err
}

func (db *realDB) ListComponentImages(clusterType, component, version string) ([]ComponentImageRow, error) {
	var result []ComponentImageRow
	conds := sq.Eq{
		"cluster_type": clusterType,
	}
	if component != "" {
		conds["component_name"] = component
	}
	if version != "" {
		conds["version"] = version
	}
	conds["tenant_id"] = 0
	sqls, args, err := sq.Select("*").From("component_images").Where(conds).ToSql()
	if err != nil {
		return result, err
	}
	err = db.Select(&result, sqls, args...)
	return result, err
}

func (db *realDB) UpdateComponentImage(clusterType, component, version, baseImage, tag string, tenantID uint64) error {
	_, err := db.Exec("UPDATE component_images SET base_image = ?, tag = ? WHERE cluster_type = ? AND component_name = ? AND version = ? AND tenant_id = ?", baseImage, tag, clusterType, component, version, tenantID)
	if err == sql.ErrNoRows {
		return ErrNotFound
	}
	return err
}

func (db *realDB) DeleteComponentImage(clusterType, component, version string) error {
	_, err := db.Exec("DELETE FROM component_images WHERE cluster_type = ? AND component_name = ? AND version = ?", clusterType, component, version)
	if e, ok := err.(*mysql.MySQLError); ok && e.Number == 1062 {
		return ErrAlreadyExists
	}
	return err
}

func (db *realDB) ListVersionsFromComponentImage(clusterType, component string) ([]string, error) {
	var versions []string
	err := db.Select(&versions, "SELECT DISTINCT version FROM component_images WHERE cluster_type = ? AND component_name = ?", clusterType, component)
	return versions, err
}
