package audit

import (
	"context"
	"encoding/json"
	"fmt"
	"strconv"

	"github.com/tidbcloud/devops/api"
	auditent "github.com/tidbcloud/devops/entity/audit"
	"github.com/tidbcloud/devops/entity/event"
	"github.com/tidbcloud/devops/pkg/infra/config"
	"github.com/tidbcloud/devops/pkg/infra/errs"
	"github.com/tidbcloud/devops/pkg/infra/eventbus"
	"github.com/tidbcloud/devops/pkg/infra/utils/repoopt"
	"github.com/tidbcloud/devops/pkg/service"
	"github.com/tidbcloud/devops/pkg/service/audit/repo"

	"github.com/go-logr/logr"
	"github.com/tidbcloud/pkgv2/db"
	"go.uber.org/dig"
)

const (
	groupKey = "devops_audit_service"
)

var (
	_ AuditService = &svc{}
)

type ServiceParam struct {
	dig.In

	Conf     *config.RootConfig
	Logger   logr.Logger
	DB       db.TransProvider
	EventBus *eventbus.EventBus
}

type ServiceResult struct {
	dig.Out

	API AuditServiceAPI
}

func NewService(p ServiceParam) (ServiceResult, error) {
	base := service.NewBaseService("AuditService")

	s := &svc{
		BaseService: base,

		dbp: p.DB,
	}

	s.repo = repo.NewAuditLogRepo(p.DB)

	p.EventBus.ChangeEvent.Subscribe(groupKey, s.HandleChangeEvent)
	p.EventBus.ClusterMaintenanceEvent.Subscribe(groupKey, s.HandleClusterMaintenaceEvent)
	p.EventBus.OperationEvent.Subscribe(groupKey, s.HandleOperationEvent)
	p.EventBus.JumpEvent.Subscribe(groupKey, s.HandleJumpEvent)

	return ServiceResult{API: s}, nil
}

type svc struct {
	*service.BaseService

	dbp  db.TransProvider
	repo repo.AuditLogRepo
}

func (s *svc) ListAuditLog(ctx context.Context, param ListAuditLogParam) ([]*auditent.AuditLog, int64, error) {
	param, err := param.Normalize()
	if err != nil {
		return nil, 0, errs.APIErrorNew(api.ErrCodeInvalidArgument, err)
	}

	opts := []repoopt.Option{
		repo.WithType(param.Type),
	}
	if len(param.ObjectIDs) > 0 {
		opts = append(opts, repo.InObjectIDs(param.ObjectIDs))
	}

	total, err := s.repo.Count(ctx, opts...)
	if err != nil {
		return nil, 0, err
	}
	if param.Page > 0 {
		opts = append(opts, repo.WithPager(param.Page, param.PerPage, true))
	}
	adtLogs, err := s.repo.List(ctx, opts...)
	if err != nil {
		return nil, 0, err
	}

	return adtLogs, total, nil
}

func (s *svc) ListAuditLogTypes(_ context.Context) ([]auditent.AuditLogType, error) {
	types := append([]auditent.AuditLogType(nil), auditent.AuditLogTypeValues...)
	return types, nil
}

func (s *svc) Create(ctx context.Context, adtLog *auditent.AuditLog) error {
	return s.dbp.Transaction(ctx, func(ctx context.Context) error {
		return s.repo.Create(ctx, adtLog)
	})
}

func EventToAuditLog(base event.BaseEvent, typ auditent.AuditLogType, detail any) (*auditent.AuditLog, error) {
	detailData, err := json.Marshal(detail)
	if err != nil {
		return nil, fmt.Errorf("marshal event details: %w", err)
	}

	return &auditent.AuditLog{
		Type:      typ,
		ObjectID:  strconv.FormatUint(base.ID, 10),
		Operator:  base.OperatorEmail,
		Operation: base.EventType.String(),
		OperateAt: base.OperateAt,
		Details:   detailData,
	}, nil
}
