package repository

import (
	"context"
	"encoding/json"

	"github.com/tidbcloud/pkgv2/db"
	"gorm.io/datatypes"
	"gorm.io/gorm"

	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/repository"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/infra/repository/schema"
)

type shadowPoolRepositoryImpl struct {
	dbProvider db.Provider
}

func NewShadowPoolRepository(dbProvider db.Provider) repository.ShadowPoolRepository {
	return &shadowPoolRepositoryImpl{
		dbProvider: dbProvider,
	}
}

func (s *shadowPoolRepositoryImpl) Get(ctx context.Context, id uint64) (*entity.ShadowPool, error) {
	ctx, span := tracer.Start(ctx, "ShadowPoolRepo.Get")
	defer span.End()

	rdb := s.dbProvider.ReadDB(ctx)

	var shadowPool schema.ShadowPool
	err := rdb.Model(&schema.ShadowPool{}).Where("id = ?", id).Take(&shadowPool).Error
	if err != nil {
		return nil, err
	}

	return cvtShadowPoolEntity(&shadowPool)
}

func (s *shadowPoolRepositoryImpl) Delete(ctx context.Context, id uint64) error {
	ctx, span := tracer.Start(ctx, "ShadowPoolRepo.Delete")
	defer span.End()

	wdb := s.dbProvider.WriteDB(ctx)

	err := wdb.Model(&schema.ShadowPool{}).Where("id = ?", id).Delete(&schema.ShadowPool{}).Error
	if err != nil {
		return err
	}
	return nil
}

func (s *shadowPoolRepositoryImpl) Create(ctx context.Context, shadowPool *entity.ShadowPool) error {
	ctx, span := tracer.Start(ctx, "ShadowPoolRepo.Create")
	defer span.End()

	wdb := s.dbProvider.WriteDB(ctx)

	shadowPoolRecord, err := cvtShadowPoolRecord(shadowPool)
	if err != nil {
		return err
	}

	err = wdb.Create(shadowPoolRecord).Error
	if err != nil {
		return err
	}

	shadowPool.ID = shadowPoolRecord.ID
	return nil
}

func (s *shadowPoolRepositoryImpl) List(ctx context.Context, filter *repository.ShadowPoolFilter) ([]*entity.ShadowPool, error) {
	ctx, span := tracer.Start(ctx, "ShadowPoolRepo.List")
	defer span.End()

	rdb := s.dbProvider.ReadDB(ctx)

	var shadowPools []*schema.ShadowPool

	if filter == nil {
		filter = &repository.ShadowPoolFilter{}
	}

	tx := rdb.Model(&schema.ShadowPool{})
	tx = filterShadowPoolQuery(tx, filter)

	// Sort by priority (lower priority value = higher priority) and then by ID
	tx = tx.Order("priority ASC, id ASC")

	err := tx.Find(&shadowPools).Error
	if err != nil {
		return nil, err
	}

	if len(shadowPools) == 0 {
		return []*entity.ShadowPool{}, nil
	}

	shadowPoolEntities := make([]*entity.ShadowPool, 0, len(shadowPools))
	for _, shadowPool := range shadowPools {
		se, err := cvtShadowPoolEntity(shadowPool)
		if err != nil {
			return nil, err
		}
		shadowPoolEntities = append(shadowPoolEntities, se)
	}

	return shadowPoolEntities, nil
}

func (s *shadowPoolRepositoryImpl) Update(ctx context.Context, shadowPool *entity.ShadowPool) error {
	ctx, span := tracer.Start(ctx, "ShadowPoolRepo.Update")
	defer span.End()

	wdb := s.dbProvider.WriteDB(ctx)

	shadowPoolRecord, err := cvtShadowPoolRecord(shadowPool)
	if err != nil {
		return err
	}

	err = wdb.Model(&schema.ShadowPool{}).Where("id = ?", shadowPool.ID).Updates(shadowPoolRecord).Error
	if err != nil {
		return err
	}
	return nil
}

func filterShadowPoolQuery(tx *gorm.DB, filter *repository.ShadowPoolFilter) *gorm.DB {
	if len(filter.IDs) > 0 {
		tx = tx.Where("id in ?", filter.IDs)
	}
	if len(filter.OrgIDs) > 0 {
		tx = tx.Where("organization_id in ?", filter.OrgIDs)
	}
	if len(filter.ProjectIDs) > 0 {
		tx = tx.Where("project_id in ?", filter.ProjectIDs)
	}
	if len(filter.ServicePlans) > 0 {
		tx = tx.Where("service_plan in ?", filter.ServicePlans)
	}
	if len(filter.CloudProviders) > 0 {
		tx = tx.Where("cloud_provider in ?", filter.CloudProviders)
	}
	if len(filter.Regions) > 0 {
		tx = tx.Where("region in ?", filter.Regions)
	}
	if filter.OnlyEnabled {
		tx = tx.Where("enabled = ?", true)
	}
	return tx
}

func cvtShadowPoolRecord(shadowPool *entity.ShadowPool) (*schema.ShadowPool, error) {
	autoScalingConfigJSON, err := json.Marshal(shadowPool.AutoScalingConfig)
	if err != nil {
		return nil, err
	}

	instanceDemandJSON, err := json.Marshal(shadowPool.InstanceDemand)
	if err != nil {
		return nil, err
	}

	initSQLsJSON, err := json.Marshal(shadowPool.InitSQLs)
	if err != nil {
		return nil, err
	}

	record := &schema.ShadowPool{
		CreatedAt:         shadowPool.CreatedAt,
		UpdatedAt:         shadowPool.UpdatedAt,
		Name:              shadowPool.Name,
		OrganizationID:    shadowPool.OrganizationID,
		ProjectID:         shadowPool.ProjectID,
		Region:            shadowPool.Region,
		CloudProvider:     shadowPool.CloudProvider,
		ServicePlan:       shadowPool.ServicePlan,
		Priority:          shadowPool.Priority,
		Enabled:           shadowPool.Enabled,
		MinCapacity:       shadowPool.MinCapacity,
		MaxCapacity:       shadowPool.MaxCapacity,
		AutoScalingConfig: autoScalingConfigJSON,
		InstanceDemand:    instanceDemandJSON,
		InitSQLs:          initSQLsJSON,
	}
	record.ID = shadowPool.ID
	return record, nil
}

func cvtShadowPoolEntity(shadowPoolRecord *schema.ShadowPool) (*entity.ShadowPool, error) {
	var autoScalingConfig entity.AutoScalingConfig
	if shadowPoolRecord.AutoScalingConfig.String() != "" {
		err := json.Unmarshal(shadowPoolRecord.AutoScalingConfig, &autoScalingConfig)
		if err != nil {
			return nil, err
		}
	}

	var instanceDemand entity.InstanceDemand
	err := json.Unmarshal(shadowPoolRecord.InstanceDemand, &instanceDemand)
	if err != nil {
		return nil, err
	}

	initSQLs, err := parseInitSQLs(shadowPoolRecord.InitSQLs)
	if err != nil {
		return nil, err
	}

	return &entity.ShadowPool{
		ID:                shadowPoolRecord.ID,
		CreatedAt:         shadowPoolRecord.CreatedAt,
		UpdatedAt:         shadowPoolRecord.UpdatedAt,
		Name:              shadowPoolRecord.Name,
		OrganizationID:    shadowPoolRecord.OrganizationID,
		ProjectID:         shadowPoolRecord.ProjectID,
		Region:            shadowPoolRecord.Region,
		CloudProvider:     shadowPoolRecord.CloudProvider,
		ServicePlan:       shadowPoolRecord.ServicePlan,
		Priority:          shadowPoolRecord.Priority,
		Enabled:           shadowPoolRecord.Enabled,
		MinCapacity:       shadowPoolRecord.MinCapacity,
		MaxCapacity:       shadowPoolRecord.MaxCapacity,
		AutoScalingConfig: autoScalingConfig,
		InstanceDemand:    instanceDemand,
		InitSQLs:          initSQLs,
	}, nil
}

func parseInitSQLs(raw datatypes.JSON) ([]string, error) {
	if raw.String() == "" || raw.String() == "null" {
		return nil, nil
	}
	var initSQLs []string
	if err := json.Unmarshal(raw, &initSQLs); err != nil {
		return nil, err
	}
	return initSQLs, nil
}
