package mysql

import (
	"context"
	"fmt"
	"time"

	"github.com/jmoiron/sqlx"
	"github.com/tidbcloud/pov-service/internal/common/options"
	"github.com/tidbcloud/pov-service/internal/model"
	"github.com/tidbcloud/pov-service/internal/repo"
)

type AuditLog struct {
	ID int64 `db:"id"`

	// Operator Meta, the operator could be service or user...
	OperatorType string `db:"operator_type"`
	OperatorID   string `db:"operator_id"`

	// Type should be unique between different object types.
	// Like create_cluster and create_workload
	Type string `db:"type"`

	// Object ID is the object being operated, object type could be inferred
	// from type.
	ObjectID string `db:"object_id"`

	OperateAt time.Time `db:"operate_at"`

	// detail should be in json format
	DetailJSON string `db:"detail"`
}

const (
	AuditColumns = `
		id, operator_type, operator_id, type, object_id, operate_at, detail
	`
)

type auditLogRepo struct {
	db *sqlx.DB
}

func NewAuditLogRepo(db *sqlx.DB) repo.AuditLog {
	return &auditLogRepo{
		db: db,
	}
}

// Create implements repo.AuditLog.
// Audit log makes no sense to be feteched immediately after creation, so we ignore the saved audit log for better performance
func (a *auditLogRepo) Create(ctx context.Context, auditLog model.AuditLog) error {
	query := `
		INSERT INTO audit_log (
			operator_type, operator_id, type,
			object_id, operate_at, detail
	) VALUES (?, ?, ?, ?, ?, ?)`

	_, err := a.db.ExecContext(ctx, query,
		auditLog.OperatorMeta.Type,
		auditLog.OperatorMeta.ID,
		auditLog.Type,
		auditLog.ObjectID,
		auditLog.OperateAt,
		auditLog.DetailJSON,
	)
	return err
}

// List implements repo.AuditLog.
func (a *auditLogRepo) List(ctx context.Context, opts options.ListOption[options.RefOption]) ([]model.AuditLog, error) {
	condition, args := buildQueries(opts.TableOption)
	query := fmt.Sprintf(
		"SELECT %s FROM audit_log %s", AuditColumns, condition)

	var dblogs []AuditLog
	err := a.db.SelectContext(ctx, &dblogs, query, args...)
	if err != nil {
		return nil, fmt.Errorf("failed to list active workloads: %w", err)
	}
	logs := make([]model.AuditLog, len(dblogs))
	for i := range dblogs {
		log, err := a.db2Model(dblogs[i])
		if err != nil {
			return nil, fmt.Errorf("failed to convert db record to model: %w", err)
		}

		logs[i] = log
	}

	return logs, nil
}

func (a *auditLogRepo) db2Model(dblog AuditLog) (model.AuditLog, error) {
	return model.AuditLog{
		OperatorMeta: model.OperatorMeta{
			Type: model.OperatorType(dblog.OperatorType),
			ID:   dblog.OperatorID,
		},
		Type:         model.OperateType(dblog.Type),
		ReadableType: model.OperateType(dblog.Type).ReadableString(),
		ObjectID:     dblog.ObjectID,
		OperateAt:    dblog.OperateAt,
		DetailJSON:   dblog.DetailJSON,
	}, nil
}
