// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package dbutils

import (
	"fmt"
	"strings"

	"yunion.io/x/jsonutils"
	"yunion.io/x/pkg/errors"

	"yunion.io/x/onecloud/pkg/httperrors"
)

// convert clickhouse sqlstr v1 to v2
// v1: tcp://192.168.222.4:9000?database=yunionmeter&read_timeout=10&write_timeout=20
// v2: clickhouse://username:password@host1:9000,host2:9000/database?dial_timeout=200ms&max_execution_time=60
func ClickhouseSqlStrV1ToV2(sqlstr string) (string, error) {
	if strings.HasPrefix(sqlstr, "clickhouse://") {
		// already v2 format
		return sqlstr, nil
	}
	queryPos := strings.IndexByte(sqlstr, '?')
	if queryPos <= 0 {
		return "", errors.Wrap(httperrors.ErrInputParameter, "no query string")
	}
	hostPart := sqlstr[len("tcp://"):queryPos]
	qs, err := jsonutils.ParseQueryString(sqlstr[queryPos+1:])
	if err != nil {
		return "", errors.Wrap(err, "ParseQueryString")
	}
	dbname, _ := qs.GetString("database")
	if len(dbname) == 0 {
		return "", errors.Wrap(httperrors.ErrInputParameter, "empty database")
	}
	uname, _ := qs.GetString("username")
	pword, _ := qs.GetString("password")
	if len(uname) > 0 {
		if len(pword) > 0 {
			hostPart = fmt.Sprintf("%s:%s@%s", uname, pword, hostPart)
		} else {
			hostPart = fmt.Sprintf("%s@%s", uname, hostPart)
		}
	}
	return fmt.Sprintf("clickhouse://%s/%s?dial_timeout=200ms&max_execution_time=60", hostPart, dbname), nil
}

func ClickhouseSqlStrV2ToV1(sqlstr string) (string, error) {
	if strings.HasPrefix(sqlstr, "tcp://") {
		// already v1 format
		return sqlstr, nil
	}
	hostPart := sqlstr[len("clickhouse://"):]
	queryPos := strings.IndexByte(hostPart, '?')
	if queryPos > 0 {
		hostPart = hostPart[:queryPos]
	}
	slashPos := strings.IndexByte(hostPart, '/')
	if slashPos <= 0 {
		return "", errors.Wrap(httperrors.ErrInputParameter, "no database part")
	}
	qs := make(map[string]string)
	qs["database"] = hostPart[slashPos+1:]
	hostPart = hostPart[:slashPos]
	atPos := strings.IndexByte(hostPart, '@')
	if atPos > 0 {
		authPart := hostPart[:atPos]
		hostPart = hostPart[atPos+1:]
		colonPos := strings.IndexByte(authPart, ':')
		if colonPos > 0 {
			qs["username"] = authPart[:colonPos]
			qs["password"] = authPart[colonPos+1:]
		} else {
			qs["username"] = authPart
		}
	}
	return fmt.Sprintf("tcp://%s?%s&read_timeout=10&write_timeout=20", hostPart, jsonutils.Marshal(qs).QueryString()), nil
}

func ValidateClickhouseV2Str(sqlstr string) error {
	if !strings.HasPrefix(sqlstr, "clickhouse://") {
		return errors.Wrapf(httperrors.ErrInputParameter, "must start with clickhouse://")
	}
	qsPos := strings.IndexByte(sqlstr, '?')
	if qsPos >= 0 {
		sqlstr = sqlstr[:qsPos]
	}
	slashPos := strings.IndexByte(sqlstr, '/')
	if slashPos <= 0 {
		return errors.Wrapf(httperrors.ErrInputParameter, "missing db slash")
	}
	dbName := sqlstr[slashPos+1:]
	if len(dbName) == 0 {
		return errors.Wrapf(httperrors.ErrInputParameter, "empty database name")
	}
	return nil
}

func ValidateClickhouseV1Str(sqlstr string) error {
	if !strings.HasPrefix(sqlstr, "tcp://") {
		return errors.Wrapf(httperrors.ErrInputParameter, "must start with tcp://")
	}
	qsPos := strings.IndexByte(sqlstr, '?')
	if qsPos <= 0 {
		return errors.Wrapf(httperrors.ErrInputParameter, "mising query string")
	}
	qsPart := sqlstr[qsPos+1:]
	qs, err := jsonutils.ParseQueryString(qsPart)
	if err != nil {
		return errors.Wrap(err, "ParseQueryString")
	}
	dbName, _ := qs.GetString("database")
	if len(dbName) == 0 {
		return errors.Wrapf(httperrors.ErrInputParameter, "empty database name")
	}
	return nil
}
