package history

import (
	"context"
	"strings"
	"sync"

	"google.golang.org/protobuf/types/known/timestamppb"

	"github.com/tidbcloud/account/commons/database"
	"github.com/tidbcloud/account/idl/pbgen/proto/central"

	"github.com/jinzhu/copier"

	"github.com/tidbcloud/account/internel/dao/history"

	"go.uber.org/zap"

	"github.com/tidbcloud/tiinf/monitor/tilog"

	"gorm.io/gorm"

	"github.com/tidbcloud/account/internel/dao"
	"github.com/tidbcloud/account/internel/dao/entity"
)

var (
	_ HistorySvcIface = (*HistorySvc)(nil)
)

type HistorySvcIface interface {
	ListEventHistories(ctx context.Context, filter history.EventHistoryFilter) ([]entity.EventHistory, error)
	CreateEventHistory(ctx context.Context, history *entity.EventHistory) error
	GetLastEventHistory(ctx context.Context, filter history.SingleEventHistoryFilter) (*entity.EventHistoryLast, error)
	ListEventHistoryLast(ctx context.Context, filter history.EventHistoryFilter) ([]entity.EventHistoryLast, error)
	SetUserLoginTime(ctx context.Context, opUsers *[]*central.User)
	GetUserLastHistory(ctx context.Context, userIDs []entity.ID, historyEvent entity.HistoryEvent) (map[uint64]entity.EventHistoryLast, error)
}

var (
	historySvcInitOnce sync.Once
	historySvcInstance HistorySvcIface
)

func GetHistorySvc() (HistorySvcIface, error) {
	historySvcInitOnce.Do(func() {
		historySvcInstance = NewHistorySvc(database.DBConn.DB, dao.GetDaoHub())

	})
	return historySvcInstance, nil
}

type HistorySvc struct {
	db  *gorm.DB
	hub *dao.DaoHub
}

func NewHistorySvc(db *gorm.DB, hub *dao.DaoHub) HistorySvcIface {
	return &HistorySvc{
		db:  db,
		hub: hub,
	}
}

func (s *HistorySvc) ListEventHistories(ctx context.Context, filter history.EventHistoryFilter) ([]entity.EventHistory, error) {
	tilog.Info(ctx, "HistorySvc ListEventHistory filter", zap.Any("filter", filter))
	return s.hub.History.ListEventHistories(ctx, s.db.WithContext(ctx), filter)
}

func (s *HistorySvc) CreateEventHistory(ctx context.Context, history *entity.EventHistory) error {
	tilog.Info(ctx, "HistorySvc CreateEventHistory ", zap.Any("history", history))
	historyLast := &entity.EventHistoryLast{}
	_ = copier.Copy(historyLast, history)
	s.hub.History.CreateEventHistory(ctx, s.db.WithContext(ctx), history) //nolint:errcheck

	return s.hub.History.CreateOrUpdateEventHistoryLast(ctx, s.db.WithContext(ctx), historyLast)
}

func (s *HistorySvc) CreateOrUpdateEventHistoryLast(ctx context.Context, history *entity.EventHistoryLast) error {
	tilog.Info(ctx, "HistorySvc CreateEventHistoryLast ", zap.Any("history", history))
	return s.hub.History.CreateOrUpdateEventHistoryLast(ctx, s.db.WithContext(ctx), history)
}

func (s *HistorySvc) ListEventHistoryLast(ctx context.Context, filter history.EventHistoryFilter) ([]entity.EventHistoryLast, error) {
	tilog.Info(ctx, "HistorySvc ListEventHistory filter", zap.Any("filter", filter))
	return s.hub.History.ListEventHistoryLast(ctx, s.db.WithContext(ctx), filter)

}

func (s *HistorySvc) GetLastEventHistory(ctx context.Context, filter history.SingleEventHistoryFilter) (*entity.EventHistoryLast, error) {
	tilog.Info(ctx, "HistorySvc GetLastHistory ", zap.Any("single filter", filter))
	return s.hub.History.GetLastHistoryLast(ctx, s.db.WithContext(ctx), filter)
}

//
//func (s *HistorySvc) GetAvailableLastUserVisit(ctx context.Context, userId uint64) (orgId uint64, projectId uint64, err error) {
//	tilog.Info(ctx, "HistorySvc GetLastHistory ", zap.Any("orgId", orgId), zap.Any("projectId", projectId))
//	filter := history.EventHistoryFilter{
//		IdentityIDs:   []entity.ID{entity.ID(userId)},
//		IdentityTypes: []entity.HistoryIdentityType{entity.IdentityTypeUser},
//		Events:        []entity.HistoryEvent{entity.HistoryEventVisitOrg, entity.HistoryEventVisitProject},
//	}
//
//	histories, err := s.hub.History.ListEventHistoryLast(ctx, s.db.WithContext(ctx), filter)
//	if err != nil {
//		return 0, 0, err
//	}
//	for _, h := range histories {
//		if h.TargetType == entity.HistoryTargetTypeOrg {
//			orgId = h.TargetID.Uint64()
//		}
//		if h.TargetType == entity.HistoryTargetTypeProject {
//			projectId = h.TargetID.Uint64()
//		}
//	}
//	// if user in this  org_id
//	orgFilter := role.RoleBindingFilter{
//		TargetType: []entity.TargetType{entity.RoleBindingTargetUser},
//		TargetIDs:  []entity.ID{entity.ID(userId)},
//		ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeOrg},
//		ScopeIDs:   []entity.ID{entity.ID(orgId)},
//		Status:     []string{entity.RoleBindingStatusActive},
//	}
//
//	roleBindings, err := s.hub.Role.ListRoleBindings(ctx, s.db, orgFilter)
//	if err != nil {
//		return 0, 0, err
//	}
//	if len(roleBindings) > 0 {
//		resp.OrgId = roleBindings[0].ScopeID.Uint64()
//	}
//}

func (s *HistorySvc) SetUserLoginTime(ctx context.Context, opUsers *[]*central.User) {
	users := *opUsers
	var userIDs []entity.ID
	for _, u := range users {
		userIDs = append(userIDs, entity.ID(u.Id))
	}
	//loginHistorySvc, _ := historysvc.GetHistorySvc()
	historyFilter := history.EventHistoryFilter{
		IdentityIDs:   userIDs,
		IdentityTypes: []entity.HistoryIdentityType{entity.IdentityTypeUser},
		Events:        []entity.HistoryEvent{entity.HistoryEventLogin}}

	histories, err := s.ListEventHistoryLast(ctx, historyFilter)
	if err != nil {
		return
	}
	historyMap := make(map[uint64]*entity.EventHistoryLast)
	for i, h := range histories {
		historyMap[h.IdentityID.Uint64()] = &histories[i]

	}
	for _, u := range users {
		h := historyMap[u.Id]
		if h != nil && u.Status == strings.ToUpper(string(entity.Active)) {
			u.LoginTime = timestamppb.New(*(h.UpdatedAt))
		}

	}
}
func (s *HistorySvc) GetUserLastHistory(ctx context.Context, userIDs []entity.ID, historyEvent entity.HistoryEvent) (map[uint64]entity.EventHistoryLast, error) {
	//users := *opUsers
	//var userIDs []entity.ID
	//for _, u := range users {
	//	userIDs = append(userIDs, entity.ID(u.Id))
	//}
	historyMap := make(map[uint64]entity.EventHistoryLast)

	historyFilter := history.EventHistoryFilter{
		IdentityIDs:   userIDs,
		IdentityTypes: []entity.HistoryIdentityType{entity.IdentityTypeUser},
		Events:        []entity.HistoryEvent{historyEvent}}

	histories, err := s.ListEventHistoryLast(ctx, historyFilter)
	if err != nil {
		return historyMap, err
	}

	for i, h := range histories {
		historyMap[h.IdentityID.Uint64()] = histories[i]

	}
	tilog.Info(ctx, "GetUserHistory", zap.Any("historyMap.size", len(historyMap)))
	return historyMap, err
	//for _, u := range users {
	//	h := historyMap[u.Id]
	//	if h != nil && u.Status == strings.ToUpper(string(entity.Active)) {
	//		u.LoginTime = timestamppb.New(*(h.UpdatedAt))
	//	}
	//
	//}
}
