package entity

import (
	"fmt"
	"time"

	"github.com/tidbcloud/chaos-svc/idl/pbgen/proto/chaos"
	"github.com/tidbcloud/chaos-svc/pkg/domain/constant"
	"github.com/tidbcloud/chaos-svc/pkg/domain/vo"
	"github.com/tidbcloud/pkgv2/bizerr"
)

type Backend struct {
	ProjectID   uint64
	NetworkName string
	Status      chaos.ChaosBackendStatus
	Message     string

	Type2Operation map[constant.ChaosBackendOperationType]*vo.BackendOperation
	CreatedAt      time.Time
	Versioning     int
}

func (b *Backend) StartEnable(userId uint64) bizerr.BizError {
	if b.Status != chaos.ChaosBackendStatus_CHAOS_BACKEND_STATUS_DISABLED {
		return bizerr.New(bizerr.ErrCodeParams, "cannot start enable, status=%s", b.Status)
	}
	b.Status = chaos.ChaosBackendStatus_CHAOS_BACKEND_STATUS_INSTALLING
	if err := b.startOperation(constant.ChaosBackendOperationTypeEnable, userId); err != nil {
		return err
	}
	return nil
}

func (b *Backend) StartDisable(userId uint64, now time.Time) bizerr.BizError {
	if b.Status != chaos.ChaosBackendStatus_CHAOS_BACKEND_STATUS_READY &&
		b.Status != chaos.ChaosBackendStatus_CHAOS_BACKEND_STATUS_INSTALL_FAILED &&
		b.Status != chaos.ChaosBackendStatus_CHAOS_BACKEND_STATUS_INSTALLING {
		return bizerr.New(bizerr.ErrCodeParams, "cannot finish disable, status=%s", b.Status)
	}
	if b.Status == chaos.ChaosBackendStatus_CHAOS_BACKEND_STATUS_INSTALLING {
		// finish the enable operation
		if err := b.finishOperation(constant.ChaosBackendOperationTypeEnable, now); err != nil {
			return err
		}
	}
	b.Status = chaos.ChaosBackendStatus_CHAOS_BACKEND_STATUS_UNINSTALLING
	if err := b.startOperation(constant.ChaosBackendOperationTypeDisable, userId); err != nil {
		return err
	}
	return nil
}

func (b *Backend) EnableSucceed(now time.Time) bizerr.BizError {
	if b.Status != chaos.ChaosBackendStatus_CHAOS_BACKEND_STATUS_INSTALLING {
		return bizerr.New(bizerr.ErrCodeParams, "cannot finish enable, status=%s", b.Status)
	}
	b.Status = chaos.ChaosBackendStatus_CHAOS_BACKEND_STATUS_READY
	if err := b.finishOperation(constant.ChaosBackendOperationTypeEnable, now); err != nil {
		return err
	}
	return nil
}

func (b *Backend) EnableFailed(msg string, now time.Time) bizerr.BizError {
	if b.Status != chaos.ChaosBackendStatus_CHAOS_BACKEND_STATUS_INSTALLING {
		return bizerr.New(bizerr.ErrCodeParams, "cannot set enabled failed, status=%s", b.Status)
	}
	b.Status = chaos.ChaosBackendStatus_CHAOS_BACKEND_STATUS_INSTALL_FAILED
	if err := b.finishOperation(constant.ChaosBackendOperationTypeEnable, now); err != nil {
		return err
	}
	b.Message = msg
	return nil
}

// FinishDisableForce should only be used in the `SyncBackendFromInfraSvc` worker.
// When the `ChaosServer` is deleted from infra-apiserver manually, the worker should
// record the disable operation finished no matter what status it is.
func (b *Backend) FinishDisableForce(now time.Time) bizerr.BizError {
	if b.Status == chaos.ChaosBackendStatus_CHAOS_BACKEND_STATUS_DISABLED {
		return nil
	}

	if b.Status != chaos.ChaosBackendStatus_CHAOS_BACKEND_STATUS_UNINSTALLING {
		if b.Status == chaos.ChaosBackendStatus_CHAOS_BACKEND_STATUS_INSTALLING {
			// finish the enable operation
			if err := b.finishOperation(constant.ChaosBackendOperationTypeEnable, now); err != nil {
				return err
			}
		}
		// start the disable operation
		if err := b.startOperation(constant.ChaosBackendOperationTypeDisable, 0); err != nil {
			return err
		}
	}
	b.Status = chaos.ChaosBackendStatus_CHAOS_BACKEND_STATUS_DISABLED
	if err := b.finishOperation(constant.ChaosBackendOperationTypeDisable, now); err != nil {
		return err
	}
	return nil
}

func (b *Backend) startOperation(tp constant.ChaosBackendOperationType, userId uint64) bizerr.BizError {
	if b.Type2Operation[tp] != nil {
		return bizerr.New(bizerr.ErrCodeGeneralBusiness, fmt.Sprintf("already started operation, project_id=%d, network=%s, tp=%s", b.ProjectID, b.NetworkName, tp))
	}

	if b.Type2Operation == nil {
		b.Type2Operation = make(map[constant.ChaosBackendOperationType]*vo.BackendOperation)
	}
	b.Type2Operation[tp] = &vo.BackendOperation{
		ID:          0,
		Type:        tp,
		Status:      constant.ChaosBackendOperationStatusStart,
		CreatedAt:   time.Time{}, // auto set in repo_imp
		CreatedByID: userId,
		CompletedAt: nil,
	}
	return nil
}

func (b *Backend) finishOperation(tp constant.ChaosBackendOperationType, finishedAt time.Time) bizerr.BizError {
	if b.Type2Operation[tp] == nil {
		return bizerr.New(bizerr.ErrCodeGeneralBusiness, fmt.Sprintf("the operation is nil, project_id=%d, network=%s, tp=%s", b.ProjectID, b.NetworkName, tp))
	}

	op := b.Type2Operation[tp]
	op.Status = constant.ChaosBackendOperationStatusDone
	op.CompletedAt = &finishedAt
	return nil
}
