package cloudmigration

import (
	"time"

	"github.com/grafana/grafana/pkg/apimachinery/errutil"
)

var (
	ErrInternalNotImplementedError = errutil.Internal("cloudmigrations.notImplemented").Errorf("Internal server error")
	ErrFeatureDisabledError        = errutil.Internal("cloudmigrations.disabled").Errorf("Cloud migrations are disabled on this instance")
	ErrMigrationNotFound           = errutil.NotFound("cloudmigrations.sessionNotFound").Errorf("Session not found")
	ErrMigrationRunNotFound        = errutil.NotFound("cloudmigrations.migrationRunNotFound").Errorf("Migration run not found")
	ErrMigrationNotDeleted         = errutil.Internal("cloudmigrations.sessionNotDeleted").Errorf("Session not deleted")
	ErrTokenNotFound               = errutil.NotFound("cloudmigrations.tokenNotFound").Errorf("Token not found")
	ErrSnapshotNotFound            = errutil.NotFound("cloudmigrations.snapshotNotFound").Errorf("Snapshot not found")
	ErrEmptyResourceTypes          = errutil.BadRequest("cloudmigrations.emptyResourceTypes").Errorf("Resource types cannot be empty")
)

// CloudMigration domain structs

// CloudMigrationSession represents a configured migration token
type CloudMigrationSession struct {
	ID          int64  `xorm:"pk autoincr 'id'"`
	OrgID       int64  `xorm:"org_id"`
	UID         string `xorm:"uid"`
	AuthToken   string
	Slug        string
	StackID     int `xorm:"stack_id"`
	RegionSlug  string
	ClusterSlug string
	Created     time.Time
	Updated     time.Time
}

// CloudMigrationSnapshot contains all of the metadata about a snapshot
type CloudMigrationSnapshot struct {
	ID                  int64  `xorm:"pk autoincr 'id'"`
	UID                 string `xorm:"uid"`
	SessionUID          string `xorm:"session_uid"`
	Status              SnapshotStatus
	GMSPublicKey        []byte `xorm:"-"` // stored in the unified secrets table
	PublicKey           []byte `xorm:"public_key"`
	LocalDir            string `xorm:"local_directory"`
	GMSSnapshotUID      string `xorm:"gms_snapshot_uid"`
	ErrorString         string `xorm:"error_string"`
	ResourceStorageType string `xorm:"resource_storage_type"`
	EncryptionAlgo      string `xorm:"encryption_algo"`
	Metadata            []byte `xorm:"'metadata'"`
	Created             time.Time
	Updated             time.Time
	Finished            time.Time

	// Stored in the cloud_migration_resource table
	Resources []CloudMigrationResource `xorm:"-"`
	// Derived by querying the cloud_migration_resource table
	StatsRollup SnapshotResourceStats `xorm:"-"`
}

type CloudMigrationSnapshotPartition struct {
	SnapshotUID     string `xorm:"snapshot_uid"`
	ResourceType    string `xorm:"resource_type"`
	PartitionNumber int    `xorm:"partition_number"`
	Data            []byte `xorm:"data"`
}

type CloudMigrationSnapshotIndex struct {
	EncryptionAlgo string
	PublicKey      []byte
	Metadata       []byte
	Items          map[string][]int
}

type SnapshotStatus string

const (
	SnapshotStatusCreating          SnapshotStatus = "creating"
	SnapshotStatusPendingUpload     SnapshotStatus = "pending_upload"
	SnapshotStatusUploading         SnapshotStatus = "uploading"
	SnapshotStatusPendingProcessing SnapshotStatus = "pending_processing"
	SnapshotStatusProcessing        SnapshotStatus = "processing"
	SnapshotStatusFinished          SnapshotStatus = "finished"
	SnapshotStatusCanceled          SnapshotStatus = "canceled"
	SnapshotStatusError             SnapshotStatus = "error"
)

type CloudMigrationResource struct {
	ID  int64  `xorm:"pk autoincr 'id'"`
	UID string `xorm:"uid" json:"uid"`

	Name      string            `xorm:"name" json:"name"`
	Type      MigrateDataType   `xorm:"resource_type" json:"type"`
	RefID     string            `xorm:"resource_uid" json:"refId"`
	Status    ItemStatus        `xorm:"status" json:"status"`
	Error     string            `xorm:"error_string" json:"error"`
	ErrorCode ResourceErrorCode `xorm:"error_code" json:"error_code"`

	SnapshotUID string `xorm:"snapshot_uid"`
	ParentName  string `xorm:"parent_name" json:"parentName"`
}

type MigrateDataType string

const (
	DashboardDataType        MigrateDataType = "DASHBOARD"
	DatasourceDataType       MigrateDataType = "DATASOURCE"
	FolderDataType           MigrateDataType = "FOLDER"
	LibraryElementDataType   MigrateDataType = "LIBRARY_ELEMENT"
	AlertRuleType            MigrateDataType = "ALERT_RULE"
	AlertRuleGroupType       MigrateDataType = "ALERT_RULE_GROUP"
	ContactPointType         MigrateDataType = "CONTACT_POINT"
	NotificationPolicyType   MigrateDataType = "NOTIFICATION_POLICY"
	NotificationTemplateType MigrateDataType = "NOTIFICATION_TEMPLATE"
	MuteTimingType           MigrateDataType = "MUTE_TIMING"
	PluginDataType           MigrateDataType = "PLUGIN"
)

type ItemStatus string

const (
	// Returned by GMS
	ItemStatusOK    ItemStatus = "OK"
	ItemStatusError ItemStatus = "ERROR"
	// Used by default while awaiting GMS results
	ItemStatusPending ItemStatus = "PENDING"
)

type ResourceErrorCode string

const (
	ErrAlertRulesQuotaReached      ResourceErrorCode = "ALERT_RULES_QUOTA_REACHED"
	ErrAlertRulesGroupQuotaReached ResourceErrorCode = "ALERT_RULES_GROUP_QUOTA_REACHED"
	ErrDatasourceNameConflict      ResourceErrorCode = "DATASOURCE_NAME_CONFLICT"
	ErrDatasourceInvalidURL        ResourceErrorCode = "DATASOURCE_INVALID_URL"
	ErrDatasourceAlreadyManaged    ResourceErrorCode = "DATASOURCE_ALREADY_MANAGED"
	ErrFolderNameConflict          ResourceErrorCode = "FOLDER_NAME_CONFLICT"
	ErrDashboardAlreadyManaged     ResourceErrorCode = "DASHBOARD_ALREADY_MANAGED"
	ErrLibraryElementNameConflict  ResourceErrorCode = "LIBRARY_ELEMENT_NAME_CONFLICT"
	ErrUnsupportedDataType         ResourceErrorCode = "UNSUPPORTED_DATA_TYPE"
	ErrResourceConflict            ResourceErrorCode = "RESOURCE_CONFLICT"
	ErrUnexpectedStatus            ResourceErrorCode = "UNEXPECTED_STATUS_CODE"
	ErrInternalServiceError        ResourceErrorCode = "INTERNAL_SERVICE_ERROR"
	ErrGeneric                     ResourceErrorCode = "GENERIC_ERROR"
)

type SnapshotResourceStats struct {
	CountsByType   map[MigrateDataType]int
	CountsByStatus map[ItemStatus]int
	Total          int
}

// Deprecated, use GetSnapshotResult for the async workflow
func (s CloudMigrationSnapshot) GetResult() (*MigrateDataResponse, error) {
	result := MigrateDataResponse{
		RunUID: s.UID,
		Items:  s.Resources,
	}
	return &result, nil
}

func (s CloudMigrationSnapshot) ShouldQueryGMS() bool {
	return s.Status == SnapshotStatusPendingProcessing || s.Status == SnapshotStatusProcessing
}

type CloudMigrationRunList struct {
	Runs []MigrateDataResponseList
}

type CloudMigrationSessionRequest struct {
	AuthToken string
	// OrgId in the on prem instance
	OrgID int64
}

type CloudMigrationSessionResponse struct {
	UID     string
	Slug    string
	Created time.Time
	Updated time.Time
}

type CloudMigrationSessionListResponse struct {
	Sessions []CloudMigrationSessionResponse
}

type ResultSortColumn string

const (
	SortColumnID     ResultSortColumn = "id"
	SortColumnName   ResultSortColumn = "name"
	SortColumnType   ResultSortColumn = "resource_type"
	SortColumnStatus ResultSortColumn = "status"
)

type SortOrder string

const (
	SortOrderAsc  SortOrder = "ASC"
	SortOrderDesc SortOrder = "DESC"
)

// ResultPage should be in the range [1, 10000]
type ResultPage int

// ResultLimit should be in the rage [1, 10000]
type ResultLimit int

type SnapshotResultQueryParams struct {
	ResultPage  ResultPage
	ResultLimit ResultLimit
	SortColumn  ResultSortColumn
	SortOrder   SortOrder
	ErrorsOnly  bool
}

type ResourceTypes map[MigrateDataType]struct{}

func (r ResourceTypes) Has(t MigrateDataType) bool {
	_, ok := r[t]
	return ok
}

type CreateSnapshotCommand struct {
	SessionUID    string
	ResourceTypes ResourceTypes
}

type GetSnapshotsQuery struct {
	SnapshotUID string
	OrgID       int64
	SessionUID  string
	SnapshotResultQueryParams
}

type ListSnapshotsQuery struct {
	SessionUID string
	OrgID      int64
	Page       int
	Limit      int
	Sort       string
}

type UpdateSnapshotCmd struct {
	UID       string
	SessionID string
	Status    SnapshotStatus
	PublicKey []byte

	// LocalResourcesToCreate represents the local state of a resource before it has been uploaded to GMS
	LocalResourcesToCreate []CloudMigrationResource
	// CloudResourcesToUpdate represents resource state from GMS, to be merged with the local state
	CloudResourcesToUpdate []CloudMigrationResource
}

// access token

type CreateAccessTokenResponse struct {
	Token string
}

type Base64EncodedTokenPayload struct {
	Token    string
	Instance Base64HGInstance
}

func (p Base64EncodedTokenPayload) ToMigration(orgID int64) CloudMigrationSession {
	return CloudMigrationSession{
		AuthToken:   p.Token,
		Slug:        p.Instance.Slug,
		StackID:     p.Instance.StackID,
		RegionSlug:  p.Instance.RegionSlug,
		ClusterSlug: p.Instance.ClusterSlug,
		OrgID:       orgID,
	}
}

type Base64HGInstance struct {
	StackID     int
	Slug        string
	RegionSlug  string
	ClusterSlug string
}

// GMS domain structs

type MigrateDataRequest struct {
	Items           []MigrateDataRequestItem
	ItemParentNames map[MigrateDataType]map[string](string)
}

type MigrateDataRequestItem struct {
	Type  MigrateDataType
	RefID string
	Name  string
	Data  interface{}
}

type MigrateDataResponse struct {
	RunUID string
	Items  []CloudMigrationResource
}

type MigrateDataResponseList struct {
	RunUID string
}

type CreateSessionResponse struct {
	SnapshotUid string
}

type StartSnapshotResponse struct {
	SnapshotID           string `json:"snapshotID"`
	MaxItemsPerPartition uint32 `json:"maxItemsPerPartition"`
	Algo                 string `json:"algo"`
	GMSPublicKey         []byte `json:"encryptionKey"`
	Metadata             []byte `json:"metadata"`
}

// Based on Grafana Migration Service DTOs
type GetSnapshotStatusResponse struct {
	State   SnapshotState            `json:"state"`
	Results []CloudMigrationResource `json:"results"`
}

type SnapshotState string

const (
	SnapshotStateInitialized SnapshotState = "INITIALIZED"
	SnapshotStateProcessing  SnapshotState = "PROCESSING"
	SnapshotStateFinished    SnapshotState = "FINISHED"
	SnapshotStateCanceled    SnapshotState = "CANCELED"
	SnapshotStateError       SnapshotState = "ERROR"
	SnapshotStateUnknown     SnapshotState = "UNKNOWN"
)

var (
	ErrTokenInvalid           = errutil.Internal("cloudmigrations.createMigration.tokenInvalid", errutil.WithPublicMessage("Token is not valid. Generate a new token on your cloud instance and try again."))
	ErrTokenRequestError      = errutil.Internal("cloudmigrations.createMigration.tokenRequestError", errutil.WithPublicMessage("An error occurred while validating the token. Please check the Grafana instance logs."))
	ErrTokenValidationFailure = errutil.Internal("cloudmigrations.createMigration.tokenValidationFailure", errutil.WithPublicMessage("Token is not valid. Please ensure the token matches the migration token on your cloud instance."))
	ErrInstanceUnreachable    = errutil.Internal("cloudmigrations.createMigration.instanceUnreachable", errutil.WithPublicMessage("The cloud instance cannot be reached. Make sure the instance is running and try again."))
	ErrInstanceRequestError   = errutil.Internal("cloudmigrations.createMigration.instanceRequestError", errutil.WithPublicMessage("An error occurred while attempting to verify the cloud instance's connectivity. Please check the network settings or cloud instance status."))
	ErrSessionCreationFailure = errutil.Internal("cloudmigrations.createMigration.sessionCreationFailure", errutil.WithPublicMessage("There was an error creating the migration. Please try again."))
	ErrMigrationDisabled      = errutil.Internal("cloudmigrations.createMigration.migrationDisabled", errutil.WithPublicMessage("Cloud migrations are disabled on this instance."))
)
