package widetable

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"strconv"
	"time"

	"github.com/spf13/viper"

	"github.com/pingcap/crm-bot/account"
	"github.com/pingcap/crm-bot/model"
	"github.com/pingcap/crm-bot/pkg/db"
	"github.com/pingcap/crm-bot/salesforce"
)

type WidetableManager struct {
	wbs            map[string]*Widetable
	funcRoute      map[string]UpdateFnType
	transformRoute map[string]func(context.Context, interface{}) interface{}
	ctx            context.Context
}

func allAccounts(ctx context.Context, agg *AggregationKey) {
	conf := ctx.Value("conf")
	accounts := account.CrmAccounts(conf.(*salesforce.SFTpl), false)
	for _, account := range accounts.([]map[string]interface{}) {
		agg.data[account[agg.PrimaryKey].(string)] = account
	}
}

// transform functions
func datetimeTransform(ctx context.Context, row interface{}) interface{} {
	if row == nil {
		return nil
	}

	d, _ := row.(map[string]interface{})[ctx.Value("key").(string)]
	dt, err := time.Parse("2006-01-02T15:04:05.000-0700", d.(string))
	if err != nil {
		return nil
	}

	return dt.Format("2006-01-02 15:04:05")
}

func datetimeTransformOfTimestamp(ctx context.Context, row interface{}) interface{} {
	if row == nil {
		return nil
	}

	var ts int64
	var err error
	timestamp, _ := row.(map[string]interface{})[ctx.Value("key").(string)]
	switch t := timestamp.(type) {
	case string:
		ts, err = strconv.ParseInt(t, 10, 64)
		if err != nil {
			return nil
		}
	case float64:
		ts = int64(t)
	}

	if ts > 15917727210 {
		return time.Unix(ts/1000, 0).Format("2006-01-02 15:04:05")
	} else {
		return time.Unix(ts, 0).Format("2006-01-02 15:04:05")
	}
}

func fxxkRecrevDateTransform(ctx context.Context, row interface{}) interface{} {
	if row == nil {
		return nil
	}

	rowConvert := row.(map[string]interface{})
	year, _ := rowConvert["收入确认-年"]
	month, _ := rowConvert["收入确认-月"]
	return fmt.Sprintf("%.0f-%.0f-01", year.(float64), month.(float64))
}

func currencyTransform(ctx context.Context, row interface{}) interface{} {
	if row == nil {
		return nil
	}

	amount, _ := row.(map[string]interface{})[ctx.Value("key").(string)]
	currency := ctx.Value("currency").(string)
	currency_rates := ctx.Value("currency-rate")
	for _, tmp := range currency_rates.([]model.PingcapCrmCurrencyRate) {
		if tmp.Currency == currency {
			return amount.(float64) / tmp.Rate
		}
	}

	log.Println("[error] currencyTransform currency:", currency)
	return amount
}

func (wbg *WidetableManager) Init() {
	wbg.funcRoute = make(map[string]UpdateFnType)
	wbg.funcRoute["account"] = allAccounts

	wbg.transformRoute = make(map[string]func(context.Context, interface{}) interface{})
	wbg.transformRoute["datetimeTransformOfTimestamp"] = datetimeTransformOfTimestamp
	wbg.transformRoute["datetimeTransform"] = datetimeTransform
	wbg.transformRoute["currencyTransform"] = currencyTransform
	wbg.transformRoute["fxxkRecrevDateTransform"] = fxxkRecrevDateTransform

	currencyRate, _ := salesforce.GetCurrencyRate()
	wbg.ctx = context.WithValue(context.Background(), "currency-rate", currencyRate)

	widetableConfs := []model.WidetableCfg{}
	_ = viper.UnmarshalKey("widetables", &widetableConfs)
	wbg.wbs = map[string]*Widetable{}
	for _, conf := range widetableConfs {
		wb := wbg.NewWidetable(conf)
		if wb != nil {
			wbg.wbs[conf.Name] = wb
		}
	}
}

func (wbg *WidetableManager) NewWidetable(cfg model.WidetableCfg) *Widetable {
	bytesData, err := os.ReadFile(cfg.Path)
	if err != nil {
		return nil
	}

	wb := Widetable{Wbg: wbg}
	err = json.Unmarshal(bytesData, &wb)
	if err != nil {
		return nil
	}
	wb.exists = make(map[string]string)

	dbCfgs := []model.DatabaseCfg{}
	_ = viper.UnmarshalKey("databases", &dbCfgs)
	for _, cfg := range dbCfgs {
		if wb.DataBaseName == cfg.Database {
			wb.Gdb, err = db.NewDb(cfg.URL)
			if err != nil {
				log.Println("[error] ", err)
				return nil
			}
			break
		}
	}
	if wb.Gdb == nil {
		log.Panicln("[error] database not found ", wb.DataBaseName)
		return nil
	}

	err = wb.AggKey.Init(wbg.funcRoute)
	if err != nil {
		log.Println("[error] create widetable ", cfg.Path, err)
		return nil
	}

	wb.ch = make(chan []string, 20)
	wb.transformRoute = wbg.transformRoute
	wbg.wbs[cfg.Name] = &wb

	return &wb
}

func (wbg *WidetableManager) GetWidetableByName(name string) *Widetable {
	if wb, ok := wbg.wbs[name]; ok {
		return wb
	}
	return nil
}

func (wbg *WidetableManager) Flow(fn func(wbs map[string]*Widetable)) {
	fn(wbg.wbs)
}

func (wbg *WidetableManager) Start() error {
	for _, wb := range wbg.wbs {
		err := wb.Gdb.AutoMigrate(&model.AccountWideTable{}, &model.TenantWideTable{})
		if err != nil {
			return err
		}
		wb.Import()
	}
	return nil
}

func NewWidetableManager() *WidetableManager {
	wbg := &WidetableManager{}
	wbg.Init()
	return wbg
}
