package migrations

import (
	"fmt"
	"net/url"
	"os"
	"time"

	"xorm.io/xorm"

	"github.com/grafana/grafana/pkg/components/simplejson"
	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/services/datasources"
	"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
	"github.com/grafana/grafana/pkg/setting"
	"github.com/grafana/grafana/pkg/util"
)

func AddExternalAlertmanagerToDatasourceMigration(mg *migrator.Migrator) {
	mg.AddMigration("migrate external alertmanagers to datsourcse", &externalAlertmanagerToDatasources{})
}

type externalAlertmanagerToDatasources struct {
	migrator.MigrationBase
}

type AdminConfiguration struct {
	OrgID int64 `xorm:"org_id"`

	Alertmanagers []string

	CreatedAt int64 `xorm:"created_at"`
	UpdatedAt int64 `xorm:"updated_at"`
}

func (e externalAlertmanagerToDatasources) SQL(dialect migrator.Dialect) string {
	return "migrate external alertmanagers to datasource"
}

func (e externalAlertmanagerToDatasources) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
	var results []AdminConfiguration
	err := sess.SQL("SELECT org_id, alertmanagers, created_at, updated_at FROM ngalert_configuration").Find(&results)
	if err != nil {
		return err
	}

	for _, result := range results {
		for _, am := range removeDuplicates(result.Alertmanagers) {
			u, err := url.Parse(am)
			if err != nil {
				return err
			}
			uri := fmt.Sprintf("%s://%s%s", u.Scheme, u.Host, u.Path)

			uid, err := generateNewDatasourceUid(sess, result.OrgID)
			if err != nil {
				return err
			}
			ds := &datasources.DataSource{
				OrgID:   result.OrgID,
				Name:    fmt.Sprintf("alertmanager-%s", uid),
				Type:    "alertmanager",
				Access:  "proxy",
				URL:     uri,
				Created: time.Unix(result.CreatedAt, 0),
				Updated: time.Unix(result.UpdatedAt, 0),
				UID:     uid,
				Version: 1,
				JsonData: simplejson.NewFromAny(map[string]any{
					"handleGrafanaManagedAlerts": true,
					"implementation":             "prometheus",
				}),
				SecureJsonData: map[string][]byte{},
			}

			if u.User != nil {
				ds.BasicAuth = true
				ds.BasicAuthUser = u.User.Username()
				if password, ok := u.User.Password(); ok {
					ds.SecureJsonData = getEncryptedJsonData(mg.Cfg, map[string]string{
						"basicAuthPassword": password,
					}, log.New("securejsondata"))
				}
			}

			rowsAffected, err := sess.Table("data_source").Insert(ds)
			if err != nil {
				return err
			}
			if rowsAffected == 0 {
				return fmt.Errorf("expected 1 row, got %d", rowsAffected)
			}
		}
	}

	return nil
}

func removeDuplicates(strs []string) []string {
	var res []string
	found := map[string]bool{}

	for _, str := range strs {
		if found[str] {
			continue
		}
		found[str] = true
		res = append(res, str)
	}
	return res
}

func generateNewDatasourceUid(sess *xorm.Session, orgId int64) (string, error) {
	for i := 0; i < 3; i++ {
		uid := util.GenerateShortUID()

		exists, err := sess.Table("data_source").Where("uid = ? AND org_id = ?", uid, orgId).Exist()
		if err != nil {
			return "", err
		}

		if !exists {
			return uid, nil
		}
	}

	return "", datasources.ErrDataSourceFailedGenerateUniqueUid
}

// SecureJsonData is used to store encrypted data (for example in data_source table). Only values are separately
// encrypted.
type secureJsonData map[string][]byte

// getEncryptedJsonData returns map where all keys are encrypted.
func getEncryptedJsonData(cfg *setting.Cfg, sjd map[string]string, log log.Logger) secureJsonData {
	encrypted := make(secureJsonData)
	for key, data := range sjd {
		encryptedData, err := util.Encrypt([]byte(data), cfg.SecretKey)
		if err != nil {
			log.Error(err.Error())
			os.Exit(1)
		}

		encrypted[key] = encryptedData
	}
	return encrypted
}