package manager

import (
	"encoding/json"
	"io"
	"sync"
	"time"

	"github.com/google/uuid"
	"github.com/pingcap/errors"
	"github.com/pingcap/log"
	openapiv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	"gorm.io/gorm"

	"github.com/pingcap-inc/clinic/pkg/common"
	"github.com/pingcap-inc/clinic/pkg/datasource/utils"
	"github.com/pingcap-inc/clinic/pkg/db"
	"github.com/pingcap-inc/clinic/pkg/db/model"
	error2 "github.com/pingcap-inc/clinic/pkg/error"
	"github.com/pingcap-inc/clinic/pkg/event_bus"
	"github.com/pingcap-inc/clinic/pkg/events"
	"github.com/pingcap-inc/clinic/pkg/storage/iface"
)

type FileStoreageManager struct {
	mutex       sync.Mutex
	fileStorage iface.IStorage
}

func NewUploadFileManager(fs iface.IStorage) *FileStoreageManager {
	return &FileStoreageManager{
		fileStorage: fs,

		mutex: sync.Mutex{},
	}
}

func (um *FileStoreageManager) PreUploadFile(uf *db.UploadFile) (int, *error2.ClinicErr) {
	um.mutex.Lock()
	defer um.mutex.Unlock()

	f, err := model.QueryUploadFile(uf.FileUUID)
	if err != nil {
		return 0, error2.InternalError(err)
	}

	if f == nil {
		return um.initNewFile(uf)
	}

	if f.UploadStatus == string(db.StatusSuccess) {
		return 0, error2.NewError(error2.StatusConflictErr, errors.New("the file is completed"))
	}

	if err := model.UpdateUploadStatus(f.FileUUID, db.StatusRunning); err != nil {
		return 0, error2.InternalError(err)
	}

	ufp, err := model.QueryMaxFileUploadedPart(uf.FileUUID)
	if err != nil {
		return 0, error2.InternalError(err)
	}

	if ufp == nil {
		return 0, nil
	}

	return ufp.SerialNumber, nil
}

func (um *FileStoreageManager) initNewFile(uf *db.UploadFile) (int, *error2.ClinicErr) {
	/*
		dupOrgUpload, err := model.CheckAndInsertCluster(OrgID, ClusterId)
		if err != nil {
			return 0, error2.InternalError(err)
		}
		if !dupOrgUpload {
			return 0, error2.NewError(401, fmt.Errorf("not authorized action"))
		}
	*/
	uploadId, err := um.fileStorage.PreUpload(uf.FileUUID)
	if err != nil {
		return 0, error2.InternalError(err)
	}

	uf.UploadID = *uploadId
	uf.StorageType = string(um.fileStorage.Type())

	return 0, error2.InternalError(model.InsertUploadFile(uf))
}

func (um *FileStoreageManager) UploadFile(fileUUID string, content io.ReadSeeker, serialNumber int, partLen uint64) *error2.ClinicErr {
	f, err := model.QueryUploadFile(fileUUID)
	if err != nil {
		log.Error("queryload file failed")
		return error2.InternalError(err)
	}

	if f == nil {
		return error2.NotFoundError(errors.Errorf("the file record is not existed, file=%s", fileUUID))
	}
	if f.UploadStatus == string(db.StatusSuccess) {
		return error2.RequestError(errors.Errorf("the file is completed, file=%s", fileUUID))
	}

	etag, err := um.fileStorage.Upload(fileUUID, content, iface.UploadConfigRequest{
		SerialNumber: serialNumber,
		UploadId:     f.UploadID,
	})
	if err != nil {
		return error2.InternalError(err)
	}

	return error2.InternalError(model.UpsertUploadFilePart(fileUUID, serialNumber, *etag, partLen))
}

func (um *FileStoreageManager) UploadFileComplete(fileUUID string) (*db.UploadFile, *error2.ClinicErr) {
	f, err := model.QueryUploadFile(fileUUID)
	if err != nil {
		return nil, error2.InternalError(err)
	}

	if f == nil {
		return nil, error2.NotFoundError(errors.Errorf("the file record is not existed, file=%s", fileUUID))
	}

	parts, err := model.QueryUploadFileParts(fileUUID)
	if err != nil {
		return nil, error2.InternalError(err)
	}

	if len(parts) == 0 {
		return nil, error2.RequestError(errors.Errorf("miss upload parts information, file=%s", fileUUID))
	}

	etags := make([]iface.ETag, 0)
	for _, part := range parts {
		etags = append(etags, iface.ETag{
			ETag:         part.Etag,
			SerialNumber: part.SerialNumber,
		})
	}

	err = um.fileStorage.Flush(fileUUID, iface.FlushConfigRequest{
		UploadId: f.UploadID,
		Etags:    etags,
	})
	if err != nil {
		return nil, error2.InternalError(err)
	}

	err = model.UploadFileCompleted(fileUUID)
	if err != nil {
		return nil, error2.InternalError(err)
	}

	_ = model.DeleteUploadFileParts(fileUUID)

	_, err = model.AddClusterIfPresent(&db.Cluster{
		Model: gorm.Model{
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
		},
		ClusterID:   f.ClusterID,
		Name:        f.ClusterName,
		ClusterType: f.ClusterType,
		OrgID:       f.OrgID,
		Status:      utils.State2String(openapiv1.ClusterState_ACTIVE),
		StartTime:   uint64(f.BeginTime.Unix()),
		EndTime:     uint64(f.EndTime.Unix()),
	})
	if err != nil {
		return nil, error2.InternalError(err)
	}

	// todo@toto change to remote call

	rebuild, err := common.NeedToRebuild(f)
	if err != nil {
		return nil, error2.InternalError(err)
	}

	err = event_bus.PostSync(events.UploadFileCompletedEvent{FileUUID: fileUUID, ClusterID: f.ClusterID, Rebuild: rebuild})
	if err != nil {
		return nil, error2.InternalError(err)
	}

	if rebuild {
		arg, err := json.Marshal(map[string]any{
			"packae_id": f.FileUUID,
			"begin_ts":  f.BeginTime.Unix(),
			"end_ts":    f.EndTime.Unix(),
		})
		r := db.Report{
			UUID:       uuid.NewString(),
			OrgID:      f.OrgID,
			ClusterID:  f.ClusterID,
			CreatorID:  f.Creator,
			ReportType: "health2",
			Status:     string(db.StatusPending),
			Arg:        string(arg),
		}
		if err == nil {
			_ = model.InsertReport(&r)
		}
	}

	return f, nil
}

func (um *FileStoreageManager) GetFile(fileUUID, rangeParameter string) (*iface.ObjectResponse, string, *error2.ClinicErr) {
	return um.GetFileByFileUUID(fileUUID, fileUUID, rangeParameter)
}

func (um *FileStoreageManager) GetFileByFileUUID(fileUUID, fileName, rangeParameter string) (*iface.ObjectResponse, string, *error2.ClinicErr) {
	f, err := model.QueryUploadFile(fileUUID)
	if err != nil {
		return nil, "", error2.InternalError(err)
	}

	if f == nil {
		return nil, "", error2.NotFoundError(errors.Errorf("the file record is not existed, file=%s", fileUUID))
	}

	if f.ClusterID == "" {
		return nil, "", error2.ProcessingError(errors.Errorf("the file %s is processing.", fileUUID))
	}

	file, err := um.fileStorage.GetObject(fileName, f.ClusterID, iface.GetObjectRequest{
		Range: rangeParameter,
	})
	if err != nil {
		return nil, "", error2.InternalError(err)
	}

	return file, f.FileOriginalName, nil
}

func (fm *FileStoreageManager) DescribeFile(fileUUID string) (*iface.ObjectResponse, string, *error2.ClinicErr) {
	return fm.DescribeFileByFileUUID(fileUUID, fileUUID)
}

func (fm *FileStoreageManager) DescribeFileByFileUUID(fileUUID, fileName string) (*iface.ObjectResponse, string, *error2.ClinicErr) {
	f, err := model.QueryUploadFile(fileUUID)
	if err != nil {
		return nil, "", error2.InternalError(err)
	}

	if f == nil {
		return nil, "", error2.NotFoundError(errors.Errorf("the file record is not existed, file=%s", fileUUID))
	}

	if f.ClusterID == "" {
		return nil, "", error2.ProcessingError(errors.Errorf("the file %s is processing.", fileUUID))
	}

	o, err := fm.fileStorage.DescribeObject(fileName, f.ClusterID)
	if err != nil {
		return nil, "", error2.InternalError(err)
	}

	return o, f.FileOriginalName, nil
}
