package domain

import (
	"context"
	"strconv"
	"time"

	"github.com/AlekSi/pointer"
	"github.com/elliotchance/pie/v2"
	"github.com/tidbcloud/dataflow-service/app/repo/entity"
	adminapi "github.com/tidbcloud/dataflow-service/idl/pbgen/proto/adminapi/v1beta1"
	"github.com/tidbcloud/dataflow-service/idl/pbgen/proto/dataflow"
	"github.com/tidbcloud/dataflow-service/internal/constants"
	"github.com/tidbcloud/dataflow-service/internal/model/util"
	"google.golang.org/protobuf/types/known/timestamppb"
)

var (
	backupExportStatusEntity2DataflowMap = map[entity.BackupExportStatus]dataflow.BackupExport_Status{
		entity.BackupExportStatusPending:  dataflow.BackupExport_PENDING,
		entity.BackupExportStatusRunning:  dataflow.BackupExport_RUNNING,
		entity.BackupExportStatusFailed:   dataflow.BackupExport_FAILED,
		entity.BackupExportStatusSuccess:  dataflow.BackupExport_SUCCESS,
		entity.BackupExportStatusCanceled: dataflow.BackupExport_CANCELED,
	}
	backupExportStatusDataflow2EntityMap = map[dataflow.BackupExport_Status]entity.BackupExportStatus{}
)

//nolint:gochecknoinits
func init() {
	for k, v := range backupExportStatusEntity2DataflowMap {
		backupExportStatusDataflow2EntityMap[v] = k
	}
}

var backupStatusEntity2DomainMap = map[entity.BackupStatus]BackupStatus{
	entity.BackupStatusUnspecified: BackupStatusUnspecified,
	entity.BackupStatusPending:     BackupStatusPending,
	entity.BackupStatusRunning:     BackupStatusRunning,
	entity.BackupStatusFailed:      BackupStatusFailed,
	entity.BackupStatusSuccess:     BackupStatusSuccess,
	entity.BackupStatusSkipped:     BackupStatusSkipped,
	entity.BackupStatusTerminated:  BackupStatusTerminated,
}

func BackupStatusEntity2Domain(status entity.BackupStatus) BackupStatus {
	if v, ok := backupStatusEntity2DomainMap[status]; ok {
		return v
	}
	return BackupStatusUnspecified
}

var backupTypeEntity2DomainMap = map[entity.BackupType]BackupType{
	entity.BackupTypeAuto:   BackupType_Auto,
	entity.BackupTypeManual: BackupType_Manual,
}

func BackupTypeEntity2Domain(status entity.BackupType) BackupType {
	if v, ok := backupTypeEntity2DomainMap[status]; ok {
		return v
	}
	return ""
}

var backupStatusDataflow2DomainMap = map[dataflow.BackupStatus]BackupStatus{
	dataflow.BackupStatus_BACKUP_STATUS_UNSPECIFIED: BackupStatusUnspecified,
	dataflow.BackupStatus_BACKUP_STATUS_PENDING:     BackupStatusPending,
	dataflow.BackupStatus_BACKUP_STATUS_RUNNING:     BackupStatusRunning,
	dataflow.BackupStatus_BACKUP_STATUS_FAILED:      BackupStatusFailed,
	dataflow.BackupStatus_BACKUP_STATUS_SUCCESS:     BackupStatusSuccess,
	dataflow.BackupStatus_BACKUP_STATUS_SKIPPED:     BackupStatusSkipped,
	dataflow.BackupStatus_BACKUP_STATUS_TERMINIATED: BackupStatusTerminated,
}

func BackupStatusDataflow2Domain(status dataflow.BackupStatus) BackupStatus {
	if v, ok := backupStatusDataflow2DomainMap[status]; ok {
		return v
	}
	return BackupStatusUnspecified
}

var backupStatusDomain2DataflowMap = map[BackupStatus]dataflow.Backup_ExternalStatus{
	BackupStatusUnspecified: dataflow.Backup_UNKNOWN,
	BackupStatusPending:     dataflow.Backup_PENDING,
	BackupStatusRunning:     dataflow.Backup_RUNNING,
	BackupStatusFailed:      dataflow.Backup_FAILED,
	BackupStatusSuccess:     dataflow.Backup_SUCCESS,
	BackupStatusSkipped:     dataflow.Backup_FAILED,
	BackupStatusTerminated:  dataflow.Backup_FAILED,
}

func BackupStatusDomain2DataflowExternalStatus(status BackupStatus) dataflow.Backup_ExternalStatus {
	if v, ok := backupStatusDomain2DataflowMap[status]; ok {
		return v
	}
	return dataflow.Backup_UNKNOWN
}

var backupTypeDomain2DataflowMap = map[BackupType]dataflow.BackupType{
	BackupType_All:    dataflow.BackupType_ALL,
	BackupType_Auto:   dataflow.BackupType_AUTO,
	BackupType_Manual: dataflow.BackupType_MANUAL,
}

func BackupTypeDomain2Dataflow(status BackupType) dataflow.BackupType {
	if v, ok := backupTypeDomain2DataflowMap[status]; ok {
		return v
	}
	return dataflow.BackupType_AUTO
}

var backupCycleEntity2DomainMap = map[entity.BackupCycle]SnapshotBackupScheduleType{
	entity.BackupCycleWeekly: SnapshotBackupScheduleTypeWeekly,
	entity.BackupCycleDaily:  SnapshotBackupScheduleTypeDaily,
}

func BackupCycleEntity2Domain(status entity.BackupCycle) SnapshotBackupScheduleType {
	if v, ok := backupCycleEntity2DomainMap[status]; ok {
		return v
	}
	return SnapshotBackupScheduleTypeDaily
}

var WeekdayEntity2DomainMap = map[int32]WeekDay{
	0: WeekDaySun,
	1: WeekDayMon,
	2: WeekDayTue,
	3: WeekDayWed,
	4: WeekDayThu,
	5: WeekDayFri,
	6: WeekDaySat,
}

func WeekDayEntity2Domain(weekday int32) WeekDay {
	if v, ok := WeekdayEntity2DomainMap[weekday]; ok {
		return v
	}
	return WeekDaySun
}

func WeekDayEntityList2Domain(weekdayList []int32) []WeekDay {
	var weekdays []WeekDay
	for _, day := range weekdayList {
		weekdays = append(weekdays, WeekDayEntity2Domain(day))
	}
	return weekdays
}

//nolint:gomnd
var WeekdayDomain2EntityMap = map[WeekDay]int32{
	WeekDaySun: 0,
	WeekDayMon: 1,
	WeekDayTue: 2,
	WeekDayWed: 3,
	WeekDayThu: 4,
	WeekDayFri: 5,
	WeekDaySat: 6,
}

func WeekdayDomain2Entity(weekday WeekDay) int32 {
	if v, ok := WeekdayDomain2EntityMap[weekday]; ok {
		return v
	}
	return 0
}

func WeekdayDomainList2Entity(weekdayList []WeekDay) []int32 {
	var weekdays []int32
	for _, day := range weekdayList {
		weekdays = append(weekdays, WeekdayDomain2Entity(day))
	}
	return weekdays
}

var weekdayDomain2DataflowMap = map[WeekDay]dataflow.WeekDay{
	WeekDaySun: dataflow.WeekDay_SUNDAY,
	WeekDayMon: dataflow.WeekDay_MONDAY,
	WeekDayTue: dataflow.WeekDay_TUESDAY,
	WeekDayWed: dataflow.WeekDay_WEDNESDAY,
	WeekDayThu: dataflow.WeekDay_THURSDAY,
	WeekDayFri: dataflow.WeekDay_FRIDAY,
	WeekDaySat: dataflow.WeekDay_SATURDAY,
}

func WeekdayDomain2Dataflow(weekday WeekDay) dataflow.WeekDay {
	if v, ok := weekdayDomain2DataflowMap[weekday]; ok {
		return v
	}
	return dataflow.WeekDay_SUNDAY
}

func WeekdayDomainList2Dataflow(weekdayList []WeekDay) []dataflow.WeekDay {
	var weekdays []dataflow.WeekDay
	for _, day := range weekdayList {
		weekdays = append(weekdays, WeekdayDomain2Dataflow(day))
	}
	return weekdays
}

var weekdayDataflow2DomainMap = map[dataflow.WeekDay]WeekDay{
	dataflow.WeekDay_SUNDAY:    WeekDaySun,
	dataflow.WeekDay_MONDAY:    WeekDayMon,
	dataflow.WeekDay_TUESDAY:   WeekDayTue,
	dataflow.WeekDay_WEDNESDAY: WeekDayWed,
	dataflow.WeekDay_THURSDAY:  WeekDayThu,
	dataflow.WeekDay_FRIDAY:    WeekDayFri,
	dataflow.WeekDay_SATURDAY:  WeekDaySat,
}

func WeekdayDataflow2Domain(weekday dataflow.WeekDay) WeekDay {
	if v, ok := weekdayDataflow2DomainMap[weekday]; ok {
		return v
	}
	return WeekDaySun
}

func WeekdayDataflowList2Domain(weekdayList []dataflow.WeekDay) []WeekDay {
	var weekdays []WeekDay
	for _, day := range weekdayList {
		weekdays = append(weekdays, WeekdayDataflow2Domain(day))
	}
	return weekdays
}

var backupCycleDataflow2EntityMap = map[dataflow.BackupCycle]entity.BackupCycle{
	dataflow.BackupCycle_WEEKLY: entity.BackupCycleWeekly,
	dataflow.BackupCycle_DAILY:  entity.BackupCycleDaily,
}

func BackupCycleDataflow2Entity(status dataflow.BackupCycle) entity.BackupCycle {
	if v, ok := backupCycleDataflow2EntityMap[status]; ok {
		return v
	}
	return entity.BackupCycleDaily
}

var backupSettingStatusEntity2DataflowMap = map[entity.BackupSettingStatus]dataflow.BackupSettingStatus{
	entity.BackupSettingStatusCreating:      dataflow.BackupSettingStatus_Creating,
	entity.BackupSettingStatusEditingRegion: dataflow.BackupSettingStatus_Editing_Region,
	entity.BackupSettingStatusEditingPitr:   dataflow.BackupSettingStatus_Editing_PiTR,
	entity.BackupSettingStatusNormal:        dataflow.BackupSettingStatus_Normal,
	entity.BackupSettingStatusDeleted:       dataflow.BackupSettingStatus_Deleted,
}

func BackupSettingStatusEntity2Dataflow(status entity.BackupSettingStatus) dataflow.BackupSettingStatus {
	if v, ok := backupSettingStatusEntity2DataflowMap[status]; ok {
		return v
	}
	return dataflow.BackupSettingStatus_Normal
}

func ConvertBackupSettingStatusMatrixToDataflow(
	backupSettingStatus entity.BackupSettingStatus, backupSettingAllowActions []entity.BackupSettingActionType,
	pitBackupSwitchStatus entity.PitBackupSwitchStatus,
) (dataflow.BackupSettingStatus, []dataflow.BackupSettingActionType) {
	status := BackupSettingStatusEntity2Dataflow(backupSettingStatus)
	actions := pie.Map(pie.Of(backupSettingAllowActions).Filter(func(action entity.BackupSettingActionType) bool {
		return BackupSettingActionTypeEntity2Dataflow(action) != nil
	}).Result, func(action entity.BackupSettingActionType) dataflow.BackupSettingActionType {
		return *BackupSettingActionTypeEntity2Dataflow(action)
	})
	actions = append(actions, dataflow.BackupSettingActionType_Edit_Schedule)
	if status == dataflow.BackupSettingStatus_Editing_PiTR {
		if pitBackupSwitchStatus == entity.PitBackupStatusWaitingForEnabling {
			status = dataflow.BackupSettingStatus_Waiting_For_Editing_PiTR
		} else if pitBackupSwitchStatus == entity.PitBackupStatusEnabling ||
			pitBackupSwitchStatus == entity.PitBackupStatusDisabling {
			actions = []dataflow.BackupSettingActionType{dataflow.BackupSettingActionType_Edit_Schedule}
		}
	}
	if status == dataflow.BackupSettingStatus_Normal {
		if pitBackupSwitchStatus == entity.PitBackupStatusWaitingForRestarting {
			status = dataflow.BackupSettingStatus_Waiting_For_Editing_PiTR
		} else if pitBackupSwitchStatus == entity.PitBackupStatusRestarting {
			status = dataflow.BackupSettingStatus_Editing_Region
			actions = []dataflow.BackupSettingActionType{dataflow.BackupSettingActionType_Edit_Schedule}
		}
	}
	return status, actions
}

var backupSettingActionTypeEntity2DataflowMap = map[entity.BackupSettingActionType]dataflow.BackupSettingActionType{
	entity.BackupSettingActionModifyRegion: dataflow.BackupSettingActionType_Edit_Region,
	entity.BackupSettingActionModifyPitR:   dataflow.BackupSettingActionType_Edit_PiTR,
}

func BackupSettingActionTypeEntity2Dataflow(action entity.BackupSettingActionType) *dataflow.BackupSettingActionType {
	if v, ok := backupSettingActionTypeEntity2DataflowMap[action]; ok {
		return pointer.To(v)
	}
	return nil
}

var snapshotBackupScheduleDomain2DataflowMap = map[SnapshotBackupScheduleType]dataflow.SnapshotBackupScheduleType{
	SnapshotBackupScheduleTypeDaily:  dataflow.SnapshotBackupScheduleType_Daily,
	SnapshotBackupScheduleTypeWeekly: dataflow.SnapshotBackupScheduleType_Weekly,
}

func SnapshotBackupScheduleDomain2Dataflow(s SnapshotBackupScheduleType) dataflow.SnapshotBackupScheduleType {
	if v, ok := snapshotBackupScheduleDomain2DataflowMap[s]; ok {
		return v
	}
	return dataflow.SnapshotBackupScheduleType_Daily
}

var snapshotBackupScheduleDataflow2DomainMap = map[dataflow.SnapshotBackupScheduleType]SnapshotBackupScheduleType{
	dataflow.SnapshotBackupScheduleType_Daily:  SnapshotBackupScheduleTypeDaily,
	dataflow.SnapshotBackupScheduleType_Weekly: SnapshotBackupScheduleTypeWeekly,
}

func SnapshotBackupScheduleDataflow2Domain(s dataflow.SnapshotBackupScheduleType) SnapshotBackupScheduleType {
	if v, ok := snapshotBackupScheduleDataflow2DomainMap[s]; ok {
		return v
	}
	return SnapshotBackupScheduleTypeDaily
}

var snapshotBackupScheduleDomain2EntityMap = map[SnapshotBackupScheduleType]entity.BackupCycle{
	SnapshotBackupScheduleTypeDaily:  entity.BackupCycleDaily,
	SnapshotBackupScheduleTypeWeekly: entity.BackupCycleWeekly,
}

func SnapshotBackupScheduleDomain2Entity(s SnapshotBackupScheduleType) entity.BackupCycle {
	if v, ok := snapshotBackupScheduleDomain2EntityMap[s]; ok {
		return v
	}
	return entity.BackupCycleWeekly
}

func BackupExportStatusEntity2Dataflow(status entity.BackupExportStatus) dataflow.BackupExport_Status {
	if v, ok := backupExportStatusEntity2DataflowMap[status]; ok {
		return v
	}
	return dataflow.BackupExport_PENDING
}

func BackupExportEntity2Dataflow(ctx context.Context, export *entity.BackupExport) *dataflow.BackupExport {
	item := &dataflow.BackupExport{
		Id:         uint64(export.ID),
		BackupId:   uint64(export.BackupID),
		BackupName: export.Backup.Name(),
		BackupTs:   nil, // fill it later
		Provider:   export.Provider,
		Region:     export.Region,
		Name:       export.Name,
		Status:     BackupExportStatusEntity2Dataflow(export.Status),
		StatusMsg:  export.StatusMsg,
		CreatedAt:  NewTimestamp(&export.CreatedAt),
		FinishedAt: NewTimestamp(export.FinishedAt),
		Storage:    nil,
	}
	if export.Status == entity.BackupExportStatusFailed {
		item.ErrorMsg = export.StatusMsg
	}
	if ts := export.Backup.BackupTS(ctx, util.ParseTSFromTSString); ts != nil {
		item.BackupTs = timestamppb.New(time.Unix(*ts, 0))
	}
	switch export.Provider {
	case constants.CloudProviderAWS:
		item.Storage = &dataflow.BackupExport_S3_{
			S3: &dataflow.BackupExport_S3{
				TargetUri: export.FinalTargetURI(),
				Region:    export.Target.Region,
			},
		}
	case constants.CloudProviderGCP:
		item.Storage = &dataflow.BackupExport_Gcs{
			Gcs: &dataflow.BackupExport_GCS{
				TargetUri: export.FinalTargetURI(),
			},
		}
	case constants.CloudProviderAzure:
		item.Storage = &dataflow.BackupExport_AzBlob{
			AzBlob: &dataflow.BackupExport_AZBlob{
				TargetUri: export.FinalTargetURI(),
			},
		}
	}

	return item
}

func BackupExportEntity2OpenAPIBackupExport(ctx context.Context, export *entity.BackupExport) *adminapi.BackupExport {
	item := &adminapi.BackupExport{
		Id:         strconv.FormatUint(uint64(export.ID), 10),
		BackupId:   strconv.FormatUint(uint64(export.BackupID), 10),
		Name:       export.Name,
		BackupName: export.Backup.Name(),
		Provider:   export.Provider,
		Region:     export.Region,
		State:      convertBackupExportState2OpenAPIBackupExportState(export.Status),
		Message:    export.StatusMsg,
		CreateTime: NewTimestamp(&export.CreatedAt),
		FinishTime: NewTimestamp(export.FinishedAt),
	}
	if ts := export.Backup.BackupTS(ctx, util.ParseTSFromTSString); ts != nil {
		item.BackupTime = timestamppb.New(time.Unix(*ts, 0))
	}
	item.Destination = &adminapi.StorageDestination{}
	switch export.Provider {
	case constants.CloudProviderAWS:
		item.Destination.Destination = &adminapi.StorageDestination_S3{
			S3: &adminapi.StorageDestination_S3Destination{
				TargetUri: export.FinalTargetURI(),
				Region:    export.Target.Region,
			},
		}
	//  case constants.CloudProviderAzure:
	//	item.Destination.Destination = &adminapi.StorageDestination_AzBlob{
	//		AzBlob: &adminapi.StorageDestination_AzBlobDestination{
	//			TargetUri: export.FinalTargetURI(),
	//		},
	//	}
	case constants.CloudProviderGCP:
		item.Destination.Destination = &adminapi.StorageDestination_Gcs{
			Gcs: &adminapi.StorageDestination_GCSDestination{
				TargetUri: export.FinalTargetURI(),
			},
		}
	}
	return item
}

func convertBackupExportState2OpenAPIBackupExportState(status entity.BackupExportStatus) adminapi.BackupExport_State {
	switch status {
	case entity.BackupExportStatusPending:
		return adminapi.BackupExport_PENDING
	case entity.BackupExportStatusRunning:
		return adminapi.BackupExport_RUNNING
	case entity.BackupExportStatusFailed:
		return adminapi.BackupExport_FAILED
	case entity.BackupExportStatusSuccess:
		return adminapi.BackupExport_SUCCEEDED
	case entity.BackupExportStatusCanceled:
		//nolint:misspell // proto uses CANCELLED intentionally
		return adminapi.BackupExport_CANCELLED
	default:
		return adminapi.BackupExport_STATE_UNSPECIFIED
	}
}
