package project

import (
	"context"

	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

var _ ProjectDaoIface = (*ProjectDao)(nil)

type CreateProjectReq struct {
	Name           string `json:"name" form:"name"`
	OrgId          uint64 `json:"org_id" form:"org_id"`
	AwsCmekEnabled bool   `json:"aws_cmek_enabled" form:"aws_cmek_enabled"`
	IsCrossAccount bool   `json:"is_cross_account" form:"is_cross_account"`
	Type           string `json:"type" form:"type"`
	OperatorType   string `json:"operator_type" form:"operator_type"`
	OperatorID     uint64 `json:"operator_id" form:"operator_id"`
	// Used to copy roles to newly created projects. If not set, roles will not be copied
	SourceProjectId uint64 `json:"source_project_id" form:"source_project_id"`
}

const projectTableName = "projects"

type ProjectDaoIface interface {
	GetProjectByID(ctx context.Context, tx *gorm.DB, projectID uint64) (*entity.Project, error)
	ListActiveProjectsByOrgID(ctx context.Context, tx *gorm.DB, orgID uint64) ([]entity.Project, error)
	ListProjects(ctx context.Context, tx *gorm.DB, orgIDs []uint64, projectIDs []uint64, status []string) ([]entity.Project, error)
	MarkProjectAsDeletedByID(ctx context.Context, tx *gorm.DB, projectID uint64) error
	CreateProject(ctx context.Context, tx *gorm.DB, req CreateProjectReq) (*entity.Project, error)
	UpdateProjectByID(ctx context.Context, tx *gorm.DB, project *entity.Project) error
}

type ProjectDao struct{}

func NewDao() ProjectDaoIface {
	return &ProjectDao{}
}

func (d *ProjectDao) GetProjectByID(ctx context.Context, tx *gorm.DB, projectID uint64) (*entity.Project, error) {
	var project entity.Project
	err := entity.NewProjectQuerySet(tx).IDEq(entity.ID(projectID)).One(&project)

	if err != nil {
		return nil, err
	}
	tilog.Info(ctx, "GetProjectByID", zap.Any("project", project))
	return &project, nil
}

func (d *ProjectDao) ListActiveProjectsByOrgID(ctx context.Context, tx *gorm.DB, orgID uint64) ([]entity.Project, error) {
	var projects []entity.Project

	err := entity.NewProjectQuerySet(tx).TenantIDEq(entity.ID(orgID)).StatusEq(entity.ProjectStatusActive).OrderDescByCreatedAt().All(&projects)
	if err != nil {
		tilog.WithContextL(ctx).Error("failed to query active projects", zap.Uint64("orgID", orgID))
		return nil, err
	}

	return projects, nil
}

func (d *ProjectDao) ListProjectsByIDList(ctx context.Context, tx *gorm.DB, ids []uint64) ([]entity.Project, error) {
	var projects []entity.Project

	err := entity.NewProjectQuerySet(tx).
		IDIn(entity.MakeIDList(ids)...).
		OrderDescByCreatedAt().
		All(&projects)

	if err != nil {
		return nil, err
	}

	return projects, nil
}

func (d *ProjectDao) ListProjects(ctx context.Context, tx *gorm.DB, orgIDs []uint64, projectIDs []uint64, status []string) ([]entity.Project, error) {
	var projects []entity.Project

	q := entity.NewProjectQuerySet(tx)
	if len(orgIDs) > 0 {
		orgIDList := make([]entity.ID, len(orgIDs))
		for i, orgID := range orgIDs {
			orgIDList[i] = entity.ID(orgID)
		}
		q = q.TenantIDIn(orgIDList...)
	}

	if len(status) > 0 {
		statusList := make([]entity.ProjectStatus, len(status))
		for i, s := range status {
			statusList[i] = entity.ProjectStatus(s)
		}
		q = q.StatusIn(statusList...)
	}

	if len(projectIDs) > 0 {
		projectIDList := make([]entity.ID, len(projectIDs))
		for i, p := range projectIDs {
			projectIDList[i] = entity.ID(p)
		}

		q = q.IDIn(projectIDList...)
	}

	err := q.OrderDescByCreatedAt().All(&projects)
	if err != nil {
		return nil, err
	}

	return projects, nil
}

func (d *ProjectDao) MarkProjectAsDeletedByID(ctx context.Context, tx *gorm.DB, projectID uint64) error {
	var project entity.Project
	err := entity.NewProjectQuerySet(tx).IDEq(entity.ID(projectID)).One(&project)
	if err != nil {
		return err
	}

	if project.Status != entity.ProjectStatusActive {
		return errs.NewBadRequest("invalid project status", nil)
	}

	project.Status = entity.ProjectStatusDeleted
	return project.Update(tx, entity.ProjectDBSchema.Status)
}

func (d *ProjectDao) CreateProject(ctx context.Context, tx *gorm.DB, req CreateProjectReq) (*entity.Project, error) {
	count, err := entity.NewProjectQuerySet(tx).TenantIDEq(entity.ID(req.OrgId)).NameEq(req.Name).StatusEq(entity.ProjectStatusActive).Count()
	if err != nil {
		return nil, err
	}
	if count > 0 {
		return nil, errs.NewBadRequest("The project name already exists.", nil)
	}
	project := &entity.Project{
		TenantID:       entity.ID(req.OrgId),
		Name:           req.Name,
		Status:         entity.ProjectStatusActive,
		IsCrossAccount: req.IsCrossAccount,
		AwsCmekEnabled: req.AwsCmekEnabled,
		Type:           req.Type,
	}
	err = project.Create(tx)
	if err != nil {
		tilog.Error(ctx, "failed to create project", zap.Error(err))
		return nil, err
	}
	tilog.Info(ctx, "dao CreateProject result", zap.Any("project", project), zap.Any("err", err))
	return project, err
}

func (d *ProjectDao) UpdateProjectByID(ctx context.Context, tx *gorm.DB, project *entity.Project) error {
	updateMap := map[string]interface{}{}

	if project.Name != "" {
		updateMap["name"] = project.Name
	}
	if project.Type != "" {
		updateMap["type"] = project.Type
	}

	return tx.Table(projectTableName).Where("id=?", project.ID).Updates(updateMap).Error
}
