package local

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"net/url"
	"os"
	"time"

	corev1 "k8s.io/api/core/v1"

	apiresource "github.com/pingcap/test-infra/common/model/resource"
	"github.com/pingcap/test-infra/sdk/core"
	"github.com/pingcap/test-infra/sdk/resource"
)

var _ resource.MySQL = &MySQLNode{}

type MySQLNode struct {
	WorkloadNode
	// reader service can be used to debug mysql node
	ReaderService *corev1.Service
}

func (t *MySQLNode) SinkEndpoint() (*url.URL, error) {
	return t.ServiceURL(resource.DBAddr)
}

// ServiceURL can be used to get mysql endpoint
func (t *MySQLNode) ServiceURL(typ resource.TiDBAddrType) (*url.URL, error) {
	if typ != resource.DBAddr {
		return nil, fmt.Errorf("invalid TiDBAddressType: %+v", typ)
	}
	urls := []string{}
	dburls, ok := os.LookupEnv("TI_PARAM_RES_" + t.ResourceHandle.Name + "_MYSQL_URL")
	if !ok {
		return nil, errors.New("local mysql not found")
	}
	if err := json.Unmarshal([]byte(dburls), &urls); err != nil {
		return nil, err
	}
	return url.Parse(urls[0])
}

func (t *MySQLNode) PeerURLs(_ resource.TiDBAddrType) ([]*url.URL, error) {
	panic("unavailable api")
}

func (t *MySQLNode) Ready() bool {
	return t.Ping() == nil
}

// Ping the mysql node, to check health
func (t *MySQLNode) Ping() error {
	addr, err := t.ServiceURL(resource.DBAddr)
	if err != nil {
		return err
	}

	dsn := resource.BuildDSN(addr, "")
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		return err
	}
	defer db.Close()

	ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
	defer cancel()
	return db.PingContext(ctx)
}

func (t *MySQLNode) GetDB(dbName string) (db *sql.DB, err error) {
	addr, err := t.ServiceURL(resource.DBAddr)
	if err != nil {
		return nil, err
	}

	db, err = sql.Open("mysql", resource.BuildDSN(addr, dbName, "timeout=2m"))
	if err != nil {
		return nil, err
	}
	if err := db.Ping(); err != nil {
		return nil, err
	}
	return db, nil
}

type mysqlGetter struct{ handle apiresource.ResourceHandle }

func (t *mysqlGetter) Get() (core.Resource, error) {
	return &MySQLNode{
		WorkloadNode: WorkloadNode{
			WorkloadNode: apiresource.WorkloadNode{
				ResourceHandle: t.handle,
			},
		},
	}, nil
}

func init() {
	core.RegisterProvider(resource.FQDN(MySQLNode{}),
		func(handle apiresource.ResourceHandle) core.ResourceGetter { return &mysqlGetter{handle: handle} })
}
