// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 mysql is Metricbeat module for MySQL server.
*/
package mysql

import (
	"database/sql"

	"github.com/elastic/beats/metricbeat/mb"

	"github.com/go-sql-driver/mysql"
	"github.com/pkg/errors"
)

func init() {
	// Register the ModuleFactory function for the "mysql" module.
	if err := mb.Registry.AddModule("mysql", NewModule); err != nil {
		panic(err)
	}
}

func NewModule(base mb.BaseModule) (mb.Module, error) {
	// Validate that at least one host has been specified.
	config := struct {
		Hosts []string `config:"hosts"    validate:"nonzero,required"`
	}{}
	if err := base.UnpackConfig(&config); err != nil {
		return nil, err
	}

	return &base, nil
}

// ParseDSN creates a DSN (data source name) string by parsing the host.
// It validates the resulting DSN and returns an error if the DSN is invalid.
//
//   Format:  [username[:password]@][protocol[(address)]]/
//   Example: root:test@tcp(127.0.0.1:3306)/
func ParseDSN(mod mb.Module, host string) (mb.HostData, error) {
	c := struct {
		Username string `config:"username"`
		Password string `config:"password"`
	}{}
	if err := mod.UnpackConfig(&c); err != nil {
		return mb.HostData{}, err
	}

	config, err := mysql.ParseDSN(host)
	if err != nil {
		return mb.HostData{}, errors.Wrapf(err, "error parsing mysql host")
	}

	if config.User == "" {
		config.User = c.Username
	}

	if config.Passwd == "" {
		config.Passwd = c.Password
	}

	// Add connection timeouts to the DSN.
	if timeout := mod.Config().Timeout; timeout > 0 {
		config.Timeout = timeout
		config.ReadTimeout = timeout
		config.WriteTimeout = timeout
	}

	noCredentialsConfig := *config
	noCredentialsConfig.User = ""
	noCredentialsConfig.Passwd = ""

	return mb.HostData{
		URI:          config.FormatDSN(),
		SanitizedURI: noCredentialsConfig.FormatDSN(),
		Host:         config.Addr,
		User:         config.User,
		Password:     config.Passwd,
	}, nil
}

// NewDB returns a new mysql database handle. The dsn value (data source name)
// must be valid, otherwise an error will be returned.
//
//   DSN Format: [username[:password]@][protocol[(address)]]/
func NewDB(dsn string) (*sql.DB, error) {
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		return nil, errors.Wrap(err, "sql open failed")
	}
	return db, nil
}
