package deploy

import (
	"context"
	"fmt"

	"github.com/go-gormigrate/gormigrate/v2"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo"
	"github.com/tidbcloud/cluster-service-ng/pkg/tioperator"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

func init() {
	registerMigrateRecord((*migrator).updateAuditNg_20260817032902)
}

// upgrade audit-ng sidecar image for all existing TiDB Groups
func (m *migrator) updateAuditNg_20260817032902(ctx context.Context) gormigrate.MigrateFunc {
	return func(db *gorm.DB) error {
		return InvokeRunMigrateWithRetry(ctx, func() error {
			return db.Transaction(func(tx *gorm.DB) error {
				sharedPools, err := m.sharedPoolRepo.List(ctx, &repo.SharedPoolFilter{Deleted: false})
				if err != nil {
					return fmt.Errorf("failed to list shared pools: %w", err)
				}

				if len(sharedPools) == 0 {
					tilog.PlainInfo("no shared pools found, skip audit-ng migration")
					return nil
				}

				targetImage := fmt.Sprintf("%s/%s", m.csngConfig.ImageConfig.Registry, m.csngConfig.ImageConfig.AuditLogImage)

				tilog.Info(ctx, "start to upgrade audit-ng sidecar for shared pools", zap.String("targetImage", targetImage))

				for _, sharedPool := range sharedPools {
					if sharedPool.GetPhase() == apiv1.SharedPool_CREATING {
						tilog.Info(ctx, "sharedPool is creating, skip audit-ng upgrade",
							zap.String("sharedPoolId", sharedPool.GetId()),
						)
						continue
					}

					tilog.Info(ctx, "upgrading audit-ng for shared pool", zap.String("sharedPoolId", sharedPool.GetId()))

					if err := m.updateAuditNgForSharedPool(ctx, sharedPool, targetImage); err != nil {
						return fmt.Errorf("failed to update audit-ng for shared pool %s: %w", sharedPool.GetId(), err)
					}

					tilog.Info(ctx, "upgrading audit-ng completed", zap.String("sharedPoolId", sharedPool.GetId()))
				}
				return nil
			})
		})
	}
}

func (m *migrator) updateAuditNgForSharedPool(ctx context.Context, sharedPool *entity.SharedPool, targetImage string) error {
	remoteCli, err := m.dataplaneManager.GetClient(sharedPool.GetDataplaneName())
	if err != nil {
		tilog.Error(ctx, "failed to get remote client for shared pool",
			zap.String("dataplaneName", sharedPool.GetDataplaneName()),
			zap.Error(err),
		)
		return fmt.Errorf("failed to get remote client: %w", err)
	}

	clusterId := sharedPool.GetId()

	for _, comp := range sharedPool.GetComponentGroups() {
		if comp.GetComponentType() != apiv1.ComponentType_TIDB {
			continue
		}

		if comp.GetPhase() == apiv1.Component_CREATING {
			tilog.Info(ctx, "skip non-available component for audit-ng update",
				zap.String("componentId", comp.GetId()),
				zap.String("phase", comp.GetPhase().String()),
			)
			continue
		}

		if comp.GetPhase() != apiv1.Component_AVAILABLE {
			tilog.Error(ctx, "component is not in available phase, audit-ng update may fail",
				zap.String("componentId", comp.GetId()),
				zap.String("phase", comp.GetPhase().String()),
			)
			return fmt.Errorf("component %s is not in available phase, current phase: %s", comp.GetId(), comp.GetPhase().String())
		}

		_, err := tioperator.UpdateTiDBGroup(ctx, remoteCli, clusterId, comp.GetId(), []tioperator.UpdateOption{
			{Option: tioperator.OpsUpdateTiDBAuditLogImage, Value: targetImage},
		})
		if err != nil {
			tilog.Error(ctx, "failed to update audit-ng sidecar image",
				zap.String("tidbGroupId", comp.GetId()),
				zap.Error(err),
			)
			return fmt.Errorf("failed to update audit-ng sidecar image for component %s: %w", comp.GetId(), err)
		}

		tilog.Info(ctx, "audit-ng sidecar image update applied",
			zap.String("tidbGroupId", comp.GetId()),
			zap.String("targetImage", targetImage),
		)
	}

	return nil
}
