package repository

import (
	"context"
	"errors"

	"github.com/tidbcloud/pkgv2/db"
	"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"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

// ProjectMigrationTaskRepoImpl implements repository.ProjectMigrationTaskRepo using GORM.
type ProjectMigrationTaskRepoImpl struct {
	dbProvider db.Provider
}

func NewProjectMigrationTaskRepo(dbProvider db.Provider) repository.ProjectMigrationTaskRepo {
	return &ProjectMigrationTaskRepoImpl{dbProvider: dbProvider}
}

// GetByOrgID returns the migration task for the given org, or (nil, nil) when no record exists.
func (r *ProjectMigrationTaskRepoImpl) GetByOrgID(ctx context.Context, orgID uint64) (*entity.ProjectMigrationTask, error) {
	ctx, span := tracer.Start(ctx, "ProjectMigrationTaskRepoImpl.GetByOrgID")
	defer span.End()

	var model schema.ProjectMigrationTask
	err := r.dbProvider.ReadDB(ctx).Where("org_id = ?", orgID).First(&model).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "project migration task not found")
		}
		return nil, err
	}
	return cvtProjectMigrationTaskModelToEntity(&model), nil
}

// GetByID returns the migration task with the given primary key, or (nil, nil) when not found.
func (r *ProjectMigrationTaskRepoImpl) GetByID(ctx context.Context, id uint64) (*entity.ProjectMigrationTask, error) {
	ctx, span := tracer.Start(ctx, "ProjectMigrationTaskRepoImpl.GetByID")
	defer span.End()

	var model schema.ProjectMigrationTask
	err := r.dbProvider.ReadDB(ctx).Where("id = ?", id).First(&model).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "project migration task not found")
		}
		return nil, err
	}
	return cvtProjectMigrationTaskModelToEntity(&model), nil
}

// Save creates the task when ID == 0, or updates an existing record.
func (r *ProjectMigrationTaskRepoImpl) Save(ctx context.Context, task *entity.ProjectMigrationTask) error {
	ctx, span := tracer.Start(ctx, "ProjectMigrationTaskRepoImpl.Save")
	defer span.End()

	model := cvtProjectMigrationTaskEntityToModel(task)
	wdb := r.dbProvider.WriteDB(ctx)
	if model.ID == 0 {
		if err := wdb.Create(model).Error; err != nil {
			return err
		}
	} else {
		if err := wdb.Omit("created_at").Save(model).Error; err != nil {
			return err
		}
	}
	task.ID = uint64(model.ID)
	return nil
}

// --- conversion helpers ---

func cvtProjectMigrationTaskModelToEntity(m *schema.ProjectMigrationTask) *entity.ProjectMigrationTask {
	migrations := make([]*entity.ProjectMigration, 0, len(m.Migrations))
	for _, item := range m.Migrations {
		migrations = append(migrations, &entity.ProjectMigration{
			SourceProjectID:   item.SourceProjectID,
			TargetProjectID:   item.TargetProjectID,
			TargetProjectName: item.TargetProjectName,
		})
	}
	return &entity.ProjectMigrationTask{
		ID:         uint64(m.ID),
		OrgID:      m.OrgID,
		State:      entity.ProjectMigrationTaskState(m.State),
		Migrations: migrations,
	}
}

func cvtProjectMigrationTaskEntityToModel(e *entity.ProjectMigrationTask) *schema.ProjectMigrationTask {
	items := make(schema.ProjectMigrationItems, 0, len(e.Migrations))
	for _, m := range e.Migrations {
		items = append(items, schema.ProjectMigrationItem{
			SourceProjectID:   m.SourceProjectID,
			TargetProjectID:   m.TargetProjectID,
			TargetProjectName: m.TargetProjectName,
		})
	}
	var id uint
	if e.ID > 0 {
		id = uint(e.ID)
	}
	return &schema.ProjectMigrationTask{
		Model:      gorm.Model{ID: id},
		OrgID:      e.OrgID,
		State:      string(e.State),
		Migrations: items,
	}
}
