package service

import (
	"context"
	"errors"

	pingcaperr "github.com/pingcap/errors"
	"github.com/pingcap/log"
	"go.uber.org/zap"
	"gorm.io/gorm"

	apierror "github.com/pingcap-inc/clinic/pkg/api/error"
	"github.com/pingcap-inc/clinic/pkg/db"
	"github.com/pingcap-inc/clinic/pkg/pagination"
)

type UnstablePlanService interface {
	QueryStatements(ctx context.Context, dbaasOrgID, dbaasClusterID uint64, updateTimeOrder string, limit, page uint) (stmts []db.PlanInsightResultStatement, total int64, err error)
	GetStatement(ctx context.Context, dbaasOrgID, dbaasClusterID, stmtResultID uint64) (stmt *db.PlanInsightResultStatement, err error)
}

func NewUnstablePlanService() UnstablePlanService {
	return &unstablePlanService{}
}

type unstablePlanService struct{}

func (u *unstablePlanService) QueryStatements(ctx context.Context, dbaasOrgID, dbaasClusterID uint64, updateTimeOrder string, limit, page uint) (stmts []db.PlanInsightResultStatement, total int64, err error) {
	err = db.TransactionWithContext(ctx, func(tx *gorm.DB) error {
		// Build sql query.
		q := tx.Model(&db.PlanInsightResultStatement{}).
			Preload("PerformanceInsightTask").
			Preload("PlanInsightResultPlans", "is_suggested"). // Preload with condition.
			Where("plan_insight_result_statements.dbaas_org_id = ? AND plan_insight_result_statements.dbaas_cluster_id = ?", dbaasOrgID, dbaasClusterID)

		// Count # of statements that match the query.
		if err = q.Count(&total).Error; err != nil {
			return pingcaperr.Annotate(err, "count statements error")
		}

		// Pagination.
		offset := pagination.GetStartIndex(int(page), int(limit))
		q = q.Limit(int(limit)).Offset(offset).Order("plan_insight_result_statements.updated_at " + updateTimeOrder)

		// Do query.
		return q.Find(&stmts).Error
	})
	if err != nil {
		log.Error("failed to query plan insight statements",
			zap.Error(err),
			zap.Uint64("dbaasOrgID", dbaasOrgID),
			zap.Uint64("dbaasClusterID", dbaasClusterID))
		return nil, 0, apierror.ErrInternal.New("internal error: failed to query plan insight statements")
	}

	return stmts, total, nil
}

func (u *unstablePlanService) GetStatement(ctx context.Context, dbaasOrgID, dbaasClusterID, stmtID uint64) (stmt *db.PlanInsightResultStatement, err error) {
	stmt = &db.PlanInsightResultStatement{}
	err = db.TransactionWithContext(ctx, func(tx *gorm.DB) error {
		// Build sql query.
		q := tx.Model(&db.PlanInsightResultStatement{}).
			Preload("PerformanceInsightTask").
			Preload("PlanInsightResultPlans").
			Joins("LEFT JOIN plan_insight_result_plans ON plan_insight_result_statements.id = plan_insight_result_plans.statement_id").
			Where("plan_insight_result_statements.id = ?", stmtID)

		// Do query.
		return q.Take(stmt).Error
	})

	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		log.Error("failed to get plan insight statement",
			zap.Error(err),
			zap.Uint64("dbaasOrgID", dbaasOrgID),
			zap.Uint64("dbaasClusterID", dbaasClusterID))
		return nil, apierror.ErrInternal.New("internal error: failed to get plan insight statement")
	} else if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, apierror.ErrNotFound.New("plan insight statement not found")
	}
	return stmt, nil
}
