package dto

import (
	"dataservices/dbhubsdk"
	"dataservices/internal/entity"
	"fmt"
	"strconv"
	"strings"

	"github.com/pingcap/log"
	"github.com/tidbcloud/dataservices/idl/pbgen/proto/dataapi"
	"github.com/tidbcloud/dataservices/idl/pbgen/proto/regioncentral"
)

func ConvertAPIRawKeys(in *entity.DataAPIRawKey) *ShowAPIKeys {
	return &ShowAPIKeys{
		APIKeys: APIKeys{
			ID:        in.ID,
			DataAppId: in.DataAppId,
			Name:      in.Description,
			CreatedBy: in.CreatedBy,
			IsDeleted: in.IsDeleted,
			CreatedAt: in.CreatedAt,
			UpdatedAt: in.UpdatedAt,
		},
		ApiKey: in.RawApiKey,
	}
}

func ConvertEndpointList(in []*entity.Endpoint) []EndpointSummary {
	result := make([]EndpointSummary, 0, len(in))
	for _, endpoint := range in {
		result = append(result, EndpointSummary{
			ID:        endpoint.ID,
			DataappId: endpoint.DataappId,
			Name:      endpoint.Name,
			Endpoint:  endpoint.Endpoint,
			Type:      endpoint.Type,
			Method:    endpoint.Method,
			Vid:       endpoint.Vid,
		})
	}
	return result
}

func ConvertListAPIKeys(in []*entity.DataAPIKey) []*APIKeys {
	out := make([]*APIKeys, 0)
	for _, v := range in {
		t := &APIKeys{
			ID:        v.ID,
			DataAppId: v.DataAppId,
			Name:      v.Description,
			CreatedBy: v.CreatedBy,
			IsDeleted: v.IsDeleted,
			CreatedAt: v.CreatedAt,
			UpdatedAt: v.UpdatedAt,
		}
		out = append(out, t)
	}
	return out
}
func ConvertGetDataAPIAccessResp(in *entity.DataAPIAccess) *regioncentral.GetDataAPIAccessResp {
	if in == nil || in.ApiKey == nil || in.App == nil {
		return nil
	}
	appAccess := &regioncentral.AppAccessResp{
		Id:            in.App.ID,
		ExternalAppId: in.App.ExternalAppID,
	}
	res := &regioncentral.GetDataAPIAccessResp{
		App: appAccess,
	}
	return res
}

func ConvertExecSqlResp(in *dbhubsdk.QueryRes) *dataapi.ExecSqlResp {
	if in == nil {
		return nil
	}
	res := &dataapi.ExecSqlResp{
		Code:         int64(in.Code),
		Message:      in.Message,
		Rows:         convertToExecSqlRespRows(in.Rows),
		Columns:      convertToExecSqlRespColumns(in.Columns),
		StartMs:      in.StartMs,
		EndMs:        in.EndMs,
		Affect:       in.Affect,
		RowNum:       in.RowNum,
		Databases:    in.Databases,
		WarnCount:    int32(in.WarnCount),
		WarnMessages: in.WarnMessages,
	}
	return res
}

var (
	StmtTypeMap = map[dbhubsdk.StmtType]dataapi.StmtType{
		dbhubsdk.SELECT:       dataapi.StmtType_Select,
		dbhubsdk.INSERT:       dataapi.StmtType_Insert,
		dbhubsdk.UPDATE:       dataapi.StmtType_Update,
		dbhubsdk.DELETE:       dataapi.StmtType_Delete,
		dbhubsdk.DROPDATABASE: dataapi.StmtType_DropDatabase,
		dbhubsdk.DROPTABLE:    dataapi.StmtType_DropTable,
		dbhubsdk.DROPINDEX:    dataapi.StmtType_DropIndex,
		dbhubsdk.ALTERTABLE:   dataapi.StmtType_AlterTable,
		dbhubsdk.SHOW:         dataapi.StmtType_Show,
		dbhubsdk.EXPLAIN:      dataapi.StmtType_Explain,
		dbhubsdk.USE:          dataapi.StmtType_Use,
		dbhubsdk.OTHER:        dataapi.StmtType_Other,
	}
)

func ConvertExecSqlExecStmtResult(stmtSQL string, stmtArgs []interface{}, in *dbhubsdk.QueryRes, limitRows uint64) *dataapi.ExecStmtResult {
	if in == nil {
		return nil
	}
	res := &dataapi.ExecStmtResult{
		ErrCode:      int64(in.Code),
		ErrMessage:   in.Message,
		Rows:         convertToExecSqlRespRows(in.Rows),
		Columns:      convertToExecSqlRespColumns(in.Columns),
		StartMs:      in.StartMs,
		EndMs:        in.EndMs,
		Affect:       in.Affect,
		RowNum:       in.RowNum,
		Databases:    in.Databases,
		StmtType:     StmtTypeMap[in.StmtType],
		StmtSql:      BuildStmtSqlForHumanReadable(stmtSQL, stmtArgs),
		LimitRows:    limitRows,
		LastInsertId: in.LastInsertID,
	}
	return res
}

func BuildStmtSqlForHumanReadable(stmtSQL string, stmtArgs []interface{}) string {
	for _, arg := range stmtArgs {
		var value string
		switch v := arg.(type) {
		case string:
			value = strconv.Quote(v)
		default:
			value = fmt.Sprintf("%v", arg)
		}
		stmtSQL = strings.Replace(stmtSQL, "?", value, 1)
	}
	return stmtSQL
}

func ConvertListAPIKeysResp(in []RbacApiKey) *dataapi.ListAPIKeyResp {
	if in == nil {
		return nil
	}
	res := &dataapi.ListAPIKeyResp{}
	for _, v := range in {
		res.Apikeys = append(res.Apikeys, &dataapi.Apikey{
			Id:          v.Id,
			Description: v.Name,
			CreatedBy:   v.CreatedBy,
		})
	}
	return res
}

func fillDatabaseMetaRowToTableMap(tableMap map[string][]string, row []string) bool {
	if len(row) < 2 {
		return false
	}
	if _, ok := tableMap[row[0]]; !ok {
		tableMap[row[0]] = []string{}
	}

	tableMap[row[0]] = append(tableMap[row[0]], row[1])
	return true
}
func ConvertGetDatabaseMetaResp(in *dbhubsdk.QueryRes, database string) *dataapi.GetDatabaseMetaResp {
	if in == nil {
		return nil
	}
	tableMap := map[string][]string{}
	for _, row := range in.Rows {
		if !fillDatabaseMetaRowToTableMap(tableMap, row) {
			log.Error(fmt.Sprintf("invalid meta row. %+v ", row))
			return nil
		}
	}

	res := &dataapi.GetDatabaseMetaResp{}
	for table, cols := range tableMap {
		tableMeta := &dataapi.TableMeta{
			Database: database,
			Table:    table,
			Columns:  cols,
		}
		res.Metas = append(res.Metas, tableMeta)
	}
	return res
}

func ConvertGetClusterMetaResp(in *dbhubsdk.QueryRes) *dataapi.GetClusterMetaResp {
	if in == nil {
		return nil
	}
	databaseMap := map[string][][]string{}
	for _, row := range in.Rows {
		if len(row) != 3 {
			return nil
		}
		database := row[2]
		if _, ok := databaseMap[database]; !ok {
			databaseMap[database] = [][]string{}
		}
		databaseMap[database] = append(databaseMap[database], row)
	}
	res := &dataapi.GetClusterMetaResp{}
	for k, v := range databaseMap {
		item := dbhubsdk.QueryRes{
			Rows: v,
		}
		databaseMeta := ConvertGetDatabaseMetaResp(&item, k)
		res.Metas = append(res.Metas, databaseMeta.Metas...)
	}
	return res
}

func convertToExecSqlRespColumns(cols []*dbhubsdk.Column) []*dataapi.Column {
	result := make([]*dataapi.Column, len(cols))
	for i, col := range cols {
		result[i] = &dataapi.Column{
			Name:     col.Col,
			DataType: col.DataType,
			Nullable: col.Nullable,
		}
	}
	return result
}

func convertToExecSqlRespRows(rows [][]string) []*dataapi.Row {
	result := make([]*dataapi.Row, len(rows))

	for i, row := range rows {
		result[i] = &dataapi.Row{
			Items: make([]string, len(row)),
		}
		for j, item := range row {
			result[i].Items[j] = item
		}
	}
	return result
}

func ConvertDataAppInfo(appInfo *entity.DataApp) *dataapi.AppInfo {
	return &dataapi.AppInfo{
		Id:               appInfo.ID,
		OrgId:            appInfo.OrgId,
		ProjectId:        appInfo.ProjectId,
		ExternalAppId:    appInfo.ExternalAppID,
		AppName:          appInfo.AppName,
		AppType:          string(appInfo.AppType),
		DeploymentRegion: appInfo.DeploymentRegion,
		CreatedBy:        appInfo.CreatedBy,
		UpdatedBy:        appInfo.UpdatedBy,
		IsDeleted:        int32(appInfo.IsDeleted),
		CreatedAt:        appInfo.CreatedAt.Format("2006-01-02 15:04:05"),
		UpdatedAt:        appInfo.UpdatedAt.Format("2006-01-02 15:04:05"),
	}
}

func ConvertEndpointInfo(endpoints []*entity.Endpoint) []*dataapi.Endpoint {
	result := make([]*dataapi.Endpoint, 0, len(endpoints))
	for _, endpoint := range endpoints {
		result = append(result, &dataapi.Endpoint{
			Id:        endpoint.ID,
			DataappId: endpoint.DataappId,
			Name:      endpoint.Name,
			Type:      endpoint.Type,
			Method:    endpoint.Method,
			Endpoint:  endpoint.Endpoint,
		})
	}
	return result
}

func ConvertEndpointPublishHistory(in []*entity.EndpointPublishVersions) []EndpointPublishHistory {
	result := make([]EndpointPublishHistory, 0, len(in))
	for _, historyVersion := range in {
		result = append(result, EndpointPublishHistory{
			DataappId:  historyVersion.DataappId,
			EndpointId: historyVersion.EndpointId,
			Version:    historyVersion.Version,
			IsOnline:   historyVersion.IsOnline,
			UpdatedAt:  historyVersion.UpdatedAt,
		})
	}
	return result
}
