// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package disk

import (
	"context"
	"database/sql"
	"fmt"
	"sync/atomic"

	"yunion.io/x/jsonutils"
	"yunion.io/x/log"

	"yunion.io/x/onecloud/pkg/apis/compute"
	"yunion.io/x/onecloud/pkg/cloudcommon/db"
	"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
	"yunion.io/x/onecloud/pkg/compute/models"
)

type DiskCleanOverduedSnapshots struct {
	SDiskBaseTask
}

func init() {
	taskman.RegisterTask(SnapshotCleanupTask{})
}

type SnapshotCleanupTask struct {
	taskman.STask
}

func (self *SnapshotCleanupTask) taskFailed(ctx context.Context, reason *jsonutils.JSONString) {
	log.Infof("SnapshotCleanupTask failed %s", reason)
	atomic.CompareAndSwapInt32(&models.SnapshotCleanupTaskRunning, 1, 0)
	self.SetStageFailed(ctx, reason)
}

func (self *SnapshotCleanupTask) taskCompleted(ctx context.Context, data jsonutils.JSONObject) {
	log.Infof("SnapshotCleanupTask completed %s", data)
	atomic.CompareAndSwapInt32(&models.SnapshotCleanupTaskRunning, 1, 0)
	self.SetStageComplete(ctx, nil)
}

func (self *SnapshotCleanupTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
	atomic.CompareAndSwapInt32(&models.SnapshotCleanupTaskRunning, 0, 1)
	now, err := self.Params.GetTime("tick")
	if err != nil {
		self.taskFailed(ctx, jsonutils.NewString("failed get tick"))
		return
	}
	var snapshots = make([]models.SSnapshot, 0)
	err = models.SnapshotManager.Query().
		Equals("fake_deleted", false).
		Equals("created_by", compute.SNAPSHOT_AUTO).
		LE("expired_at", now).All(&snapshots)
	if err == sql.ErrNoRows {
		self.taskCompleted(ctx, nil)
		return
	} else if err != nil {
		self.taskFailed(ctx, jsonutils.NewString(fmt.Sprintf("failed get snapshot %s", err)))
		return
	}
	snapshotIds := make([]string, len(snapshots))
	for i := range snapshots {
		snapshotIds[i] = snapshots[i].Id
	}
	self.StartSnapshotsDelete(ctx, snapshotIds)
}

func (self *SnapshotCleanupTask) OnInitFailed(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
	log.Errorf("delete snapshots failed %s", data)
	self.OnInit(ctx, obj, data)
}

func (self *SnapshotCleanupTask) StartSnapshotsDelete(ctx context.Context, snapshotIds []string) {
	snapshotId := snapshotIds[0]
	snapshotIds = snapshotIds[1:]
	if len(snapshotIds) > 0 {
		self.Params.Set("snapshots", jsonutils.Marshal(snapshotIds))
	}
	self.SetStage("OnDeleteSnapshot", nil)

	iSnapshot, err := models.SnapshotManager.FetchById(snapshotId)
	if err != nil {
		self.OnDeleteSnapshotFailed(ctx, self.GetObject(), nil)
		return
	}
	snapshot := iSnapshot.(*models.SSnapshot)
	if snapshot.Status == compute.SNAPSHOT_DELETING {
		self.OnDeleteSnapshot(ctx, snapshot, nil)
		return
	}

	snapshot.SetModelManager(models.SnapshotManager, snapshot)
	err = snapshot.StartSnapshotDeleteTask(ctx, self.UserCred, false, self.GetId(), 0, 0)
	if err != nil {
		self.OnDeleteSnapshotFailed(ctx, self.GetObject(), nil)
	}
}

func (self *SnapshotCleanupTask) OnDeleteSnapshot(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
	var snapshotIds = make([]string, 0)
	err := self.Params.Unmarshal(&snapshotIds, "snapshots")
	if err != nil {
		self.taskFailed(ctx, jsonutils.NewString(err.Error()))
		return
	}
	if len(snapshotIds) > 0 {
		self.StartSnapshotsDelete(ctx, snapshotIds)
	} else {
		self.taskCompleted(ctx, nil)
	}
}

func (self *SnapshotCleanupTask) OnDeleteSnapshotFailed(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
	reason := fmt.Sprintf("snapshot delete faield %s", data)
	self.taskFailed(ctx, jsonutils.NewString(reason))
}
