/*
 * Copyright 2021 PingCAP, Inc.
 *
 * 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,
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package main

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"gorm.io/gorm"
)

type IMapper interface {
	Handle(ctx context.Context, data map[string]interface{}) error
}

type DefaultMapper struct {
	tableName string
	db        *gorm.DB
}

type DefaultMapperOpt func(*DefaultMapper) error

func NewDefaultMapper(db *gorm.DB, tableName string, opts ...DefaultMapperOpt) (*DefaultMapper, error) {
	m := &DefaultMapper{
		db:        db,
		tableName: tableName,
	}
	for _, opt := range opts {
		if err := opt(m); err != nil {
			return nil, err
		}
	}
	if len(m.tableName) == 0 {
		return nil, errors.New("table name is empty")
	}
	return m, nil
}

// Handle process a single message. it returns error if any error occurs
func (m *DefaultMapper) Handle(ctx context.Context, data map[string]interface{}) error {
	rawSQL, values := m.toSql(data)
	return m.db.WithContext(ctx).Exec(rawSQL, values...).Error
}

func (m *DefaultMapper) toSql(data map[string]interface{}) (string, []interface{}) {
	//insert into foo(id) values($1)
	cols := make([]string, 0, len(data))
	placeholder := make([]string, 0, len(data))
	vals := make([]interface{}, 0, len(data))
	// ignore the order
	for k, v := range data {
		cols = append(cols, k)
		placeholder = append(placeholder, "?")
		vals = append(vals, v)
	}
	return fmt.Sprintf("insert into %s (%s) values (%s)",
		m.tableName,
		strings.Join(cols, ","),
		strings.Join(placeholder, ","),
	), vals
}
