package v1

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"sync"

	"github.com/marmotedu/errors"
	"github.com/pingcap-inc/pinsights/internal/apiserver/store"
	"github.com/pingcap-inc/pinsights/internal/pkg/code"
	"github.com/pingcap-inc/pinsights/model"
	"github.com/pingcap-inc/pinsights/pkg/log"
	"github.com/pingcap-inc/pinsights/pkg/util/jsonc"
)

type LarkBiTables struct {
	HasMore   bool                     `json:"hasMore"`
	PageToken string                   `json:"pageToken"`
	Total     int64                    `json:"total"`
	Items     []*model.LarkBiTableItem `json:"items"`
}

type LarkBiTableRecord struct {
	Id       string                     `json:"id"`
	RecordId string                     `json:"recordId"`
	Fields   map[string]json.RawMessage `json:"fields"`
}

type LarkBiTableRecords struct {
	HasMore   bool                `json:"hasMore"`
	PageToken string              `json:"pageToken"`
	Total     int64               `json:"total"`
	Items     []LarkBiTableRecord `json:"items"`
}

type BiTableRecords struct {
	Items            []LarkBiTableRecord `json:"items"`
	OriginalJsonData json.RawMessage     `json:"originalJsonData"`
}

type LarkBiTableSrv interface {
	GetBiTables(ctx context.Context, mparams *model.GetLarkBiTablesParams) (*model.LarkBiTableListData, error)
	MGetBiTableRecords(ctx context.Context, mparams *model.MGetLarkBiTableRecordsParams) (*model.MGetLarkBiTableRecordsRes, error)
}

type larkBiTableService struct {
	store store.Factory
}

var _ LarkBiTableSrv = (*larkBiTableService)(nil)

func newLarkBiTable(srv *service) *larkBiTableService {
	return &larkBiTableService{store: srv.store}
}

func (bt *larkBiTableService) GetBiTables(ctx context.Context, mparams *model.GetLarkBiTablesParams) (*model.LarkBiTableListData, error) {
	larkCommonService := newLarkCommon()
	tat, err := larkCommonService.GetAndCacheP7STToken(ctx)
	if err != nil {
		log.Error(err.Error())
		return nil, errors.Wrap(err, "Error when get bitables bacause of the error in GetAndCacheP7STToken")
	}

	req, err := http.NewRequest("GET", LARK_OPEN_API_BASE+"/bitable/v1/apps/"+mparams.LarkAppToken+"/tables", nil)
	if err != nil {
		log.Error(err.Error())
		return nil, errors.Wrap(err, "Error when contruct request for bitables")
	}
	req.Header.Set("Authorization", "Bearer "+tat)
	resp, err := (&http.Client{}).Do(req)
	if err != nil {
		log.Error(err.Error())
		return nil, errors.Wrap(err, "Error when request lark bitables api")
	}
	defer resp.Body.Close()

	body, _ := ioutil.ReadAll(resp.Body)
	var res LarkCommonRes
	if err := json.Unmarshal(jsonc.ConvertJSON2Camel(body), &res); err != nil {
		log.Error(err.Error())
		return nil, errors.Wrap(err, "Error when unmarshal lark get bitables res")
	}

	if res.Code != 0 {
		log.Errorf("Lark API error code: %d, msg: %s", res.Code, res.Msg)
		return nil, errors.WithCode(code.ErrLarkApiBizErrorCode, "Lark Error Code: %d", res.Code)
	}

	var lbt LarkBiTables
	if err := json.Unmarshal(jsonc.ConvertJSON2Camel(res.Data), &lbt); err != nil {
		log.Error(err.Error())
		return nil, errors.Wrap(err, "Error when unmarshal lark bitables data")
	}

	return &model.LarkBiTableListData{
		Items:            lbt.Items,
		OriginalJsonData: string(res.Data),
	}, nil
}

func (bt *larkBiTableService) MGetBiTableRecords(ctx context.Context, mparams *model.MGetLarkBiTableRecordsParams) (*model.MGetLarkBiTableRecordsRes, error) {
	tableIds := strings.Split(mparams.TableIds, ",")
	larkCommonService := newLarkCommon()
	tat, err := larkCommonService.GetAndCacheP7STToken(ctx)
	if err != nil {
		log.Error(err.Error())
		return nil, errors.Wrap(err, "Error when mget bitable records bacause of the error in GetAndCacheP7STToken")
	}

	rss := make(chan *struct {
		tid              string
		items            []*model.LarkBiTableSingleRecord
		originalJsonData json.RawMessage
	})
	var wg sync.WaitGroup
	for _, tid := range tableIds {
		wg.Add(1)
		go func(tid string) {
			defer wg.Done()
			rss <- getLarkBiTableRecords(tat, mparams.LarkAppToken, tid)
		}(tid)
	}

	go func() {
		wg.Wait()
		close(rss)
	}()

	var res = make(map[string]*model.LarkBiTableRecords)
	for rsItem := range rss {
		if rsItem != nil {
			res[rsItem.tid] = &model.LarkBiTableRecords{
				OriginalJsonData: string(rsItem.originalJsonData),
				Items:            rsItem.items,
			}
		}
	}

	return &model.MGetLarkBiTableRecordsRes{
		Data: res,
	}, nil
}

func getLarkBiTableRecords(tat string, larkAppToken string, tid string) *struct {
	tid              string
	items            []*model.LarkBiTableSingleRecord
	originalJsonData json.RawMessage
} {
	var rs struct {
		tid              string
		items            []*model.LarkBiTableSingleRecord
		originalJsonData json.RawMessage
	}

	req, err := http.NewRequest("GET", LARK_OPEN_API_BASE+"/bitable/v1/apps/"+larkAppToken+"/tables/"+tid+"/records", nil)
	if err != nil {
		log.Error(errors.Wrap(err, "Error when contruct request for bitable records").Error())
		return &rs
	}
	req.Header.Set("Authorization", "Bearer "+tat)
	resp, err := (&http.Client{}).Do(req)
	if err != nil {
		log.Error(errors.Wrap(err, "Error when request lark bitable records api").Error())
		return &rs
	}
	defer resp.Body.Close()

	body, _ := ioutil.ReadAll(resp.Body)
	var res LarkCommonRes
	if err := json.Unmarshal(jsonc.ConvertJSON2Camel(body), &res); err != nil {
		log.Error(errors.Wrap(err, "Error when unmarshal lark get bitable records res").Error())
		return &rs
	}

	if res.Code != 0 {
		log.Errorf("Lark API error code: %d, msg: %s", res.Code, res.Msg)
		return &rs
	}

	var lbr LarkBiTableRecords
	if err := json.Unmarshal(jsonc.ConvertJSON2Camel(res.Data), &lbr); err != nil {
		fmt.Println(err)
		log.Error(errors.Wrap(err, "Error when unmarshal lark bitable records data").Error())
		return &rs
	}

	var ris []*model.LarkBiTableSingleRecord
	for _, it := range lbr.Items {
		fields := make(map[string]string, len(it.Fields))
		for k, f := range it.Fields {
			fields[k] = string(f)
		}
		is := &model.LarkBiTableSingleRecord{
			Id:       it.Id,
			RecordId: it.Id,
			Fields:   fields,
		}
		ris = append(ris, is)
	}

	rs.items = ris
	rs.tid = tid
	rs.originalJsonData = res.Data

	return &rs
}
