package api

import (
	"fmt"
	"github.com/sacloud/libsacloud/sacloud"
	"github.com/sacloud/libsacloud/sacloud/ostype"
	"strings"
	"time"
)

// ArchiveAPI アーカイブAPI
type ArchiveAPI struct {
	*baseAPI
	findFuncMapPerOSType map[ostype.ArchiveOSTypes]func() (*sacloud.Archive, error)
}

var (
	archiveLatestStableCentOSTags                          = []string{"current-stable", "distro-centos"}
	archiveLatestStableCentOS6Tags                         = []string{"distro-centos", "distro-ver-6.10"}
	archiveLatestStableUbuntuTags                          = []string{"current-stable", "distro-ubuntu"}
	archiveLatestStableDebianTags                          = []string{"current-stable", "distro-debian"}
	archiveLatestStableVyOSTags                            = []string{"current-stable", "distro-vyos"}
	archiveLatestStableCoreOSTags                          = []string{"current-stable", "distro-coreos"}
	archiveLatestStableRancherOSTags                       = []string{"current-stable", "distro-rancheros"}
	archiveLatestStableKusanagiTags                        = []string{"current-stable", "pkg-kusanagi"}
	archiveLatestStableSophosUTMTags                       = []string{"current-stable", "pkg-sophosutm"}
	archiveLatestStableFreeBSDTags                         = []string{"current-stable", "distro-freebsd"}
	archiveLatestStableWindows2012Tags                     = []string{"os-windows", "distro-ver-2012.2"}
	archiveLatestStableWindows2012RDSTags                  = []string{"os-windows", "distro-ver-2012.2", "windows-rds"}
	archiveLatestStableWindows2012RDSOfficeTags            = []string{"os-windows", "distro-ver-2012.2", "windows-rds", "with-office"}
	archiveLatestStableWindows2016Tags                     = []string{"os-windows", "distro-ver-2016"}
	archiveLatestStableWindows2016RDSTags                  = []string{"os-windows", "distro-ver-2016", "windows-rds"}
	archiveLatestStableWindows2016RDSOfficeTags            = []string{"os-windows", "distro-ver-2016", "windows-rds", "with-office"}
	archiveLatestStableWindows2016SQLServerWeb             = []string{"os-windows", "distro-ver-2016", "windows-sqlserver", "sqlserver-2016", "edition-web"}
	archiveLatestStableWindows2016SQLServerStandard        = []string{"os-windows", "distro-ver-2016", "windows-sqlserver", "sqlserver-2016", "edition-standard"}
	archiveLatestStableWindows2016SQLServer2017Standard    = []string{"os-windows", "distro-ver-2016", "windows-sqlserver", "sqlserver-2017", "edition-standard"}
	archiveLatestStableWindows2016SQLServerStandardAll     = []string{"os-windows", "distro-ver-2016", "windows-sqlserver", "sqlserver-2016", "edition-standard", "windows-rds", "with-office"}
	archiveLatestStableWindows2016SQLServer2017StandardAll = []string{"os-windows", "distro-ver-2016", "windows-sqlserver", "sqlserver-2017", "edition-standard", "windows-rds", "with-office"}
)

// NewArchiveAPI アーカイブAPI作成
func NewArchiveAPI(client *Client) *ArchiveAPI {
	api := &ArchiveAPI{
		baseAPI: &baseAPI{
			client: client,
			FuncGetResourceURL: func() string {
				return "archive"
			},
		},
	}

	api.findFuncMapPerOSType = map[ostype.ArchiveOSTypes]func() (*sacloud.Archive, error){
		ostype.CentOS:                              api.FindLatestStableCentOS,
		ostype.CentOS6:                             api.FindLatestStableCentOS6,
		ostype.Ubuntu:                              api.FindLatestStableUbuntu,
		ostype.Debian:                              api.FindLatestStableDebian,
		ostype.VyOS:                                api.FindLatestStableVyOS,
		ostype.CoreOS:                              api.FindLatestStableCoreOS,
		ostype.RancherOS:                           api.FindLatestStableRancherOS,
		ostype.Kusanagi:                            api.FindLatestStableKusanagi,
		ostype.SophosUTM:                           api.FindLatestStableSophosUTM,
		ostype.FreeBSD:                             api.FindLatestStableFreeBSD,
		ostype.Windows2012:                         api.FindLatestStableWindows2012,
		ostype.Windows2012RDS:                      api.FindLatestStableWindows2012RDS,
		ostype.Windows2012RDSOffice:                api.FindLatestStableWindows2012RDSOffice,
		ostype.Windows2016:                         api.FindLatestStableWindows2016,
		ostype.Windows2016RDS:                      api.FindLatestStableWindows2016RDS,
		ostype.Windows2016RDSOffice:                api.FindLatestStableWindows2016RDSOffice,
		ostype.Windows2016SQLServerWeb:             api.FindLatestStableWindows2016SQLServerWeb,
		ostype.Windows2016SQLServerStandard:        api.FindLatestStableWindows2016SQLServerStandard,
		ostype.Windows2016SQLServer2017Standard:    api.FindLatestStableWindows2016SQLServer2017Standard,
		ostype.Windows2016SQLServerStandardAll:     api.FindLatestStableWindows2016SQLServerStandardAll,
		ostype.Windows2016SQLServer2017StandardAll: api.FindLatestStableWindows2016SQLServer2017StandardAll,
	}

	return api
}

// OpenFTP FTP接続開始
func (api *ArchiveAPI) OpenFTP(id int64) (*sacloud.FTPServer, error) {
	var (
		method = "PUT"
		uri    = fmt.Sprintf("%s/%d/ftp", api.getResourceURL(), id)
		//body   = map[string]bool{"ChangePassword": reset}
		res = &sacloud.Response{}
	)

	result, err := api.action(method, uri, nil, res)
	if !result || err != nil {
		return nil, err
	}

	return res.FTPServer, nil
}

// CloseFTP FTP接続終了
func (api *ArchiveAPI) CloseFTP(id int64) (bool, error) {
	var (
		method = "DELETE"
		uri    = fmt.Sprintf("%s/%d/ftp", api.getResourceURL(), id)
	)
	return api.modify(method, uri, nil)

}

// SleepWhileCopying コピー終了まで待機
func (api *ArchiveAPI) SleepWhileCopying(id int64, timeout time.Duration) error {
	handler := waitingForAvailableFunc(func() (hasAvailable, error) {
		return api.Read(id)
	}, 0)
	return blockingPoll(handler, timeout)
}

// AsyncSleepWhileCopying コピー終了まで待機(非同期)
func (api *ArchiveAPI) AsyncSleepWhileCopying(id int64, timeout time.Duration) (chan (interface{}), chan (interface{}), chan (error)) {
	handler := waitingForAvailableFunc(func() (hasAvailable, error) {
		return api.Read(id)
	}, 0)
	return poll(handler, timeout)
}

// CanEditDisk ディスクの修正が可能か判定
func (api *ArchiveAPI) CanEditDisk(id int64) (bool, error) {

	archive, err := api.Read(id)
	if err != nil {
		return false, err
	}

	if archive == nil {
		return false, nil
	}

	// BundleInfoがあれば編集不可
	if archive.BundleInfo != nil && archive.BundleInfo.HostClass == bundleInfoWindowsHostClass {
		// Windows
		return false, nil
	}

	// SophosUTMであれば編集不可
	if archive.HasTag("pkg-sophosutm") || archive.IsSophosUTM() {
		return false, nil
	}

	for _, t := range allowDiskEditTags {
		if archive.HasTag(t) {
			// 対応OSインストール済みディスク
			return true, nil
		}
	}

	// ここまできても判定できないならソースに投げる
	if archive.SourceDisk != nil && archive.SourceDisk.Availability != "discontinued" {
		return api.client.Disk.CanEditDisk(archive.SourceDisk.ID)
	}
	if archive.SourceArchive != nil && archive.SourceArchive.Availability != "discontinued" {
		return api.client.Archive.CanEditDisk(archive.SourceArchive.ID)
	}
	return false, nil

}

// GetPublicArchiveIDFromAncestors 祖先の中からパブリックアーカイブのIDを検索
func (api *ArchiveAPI) GetPublicArchiveIDFromAncestors(id int64) (int64, bool) {

	emptyID := int64(0)

	archive, err := api.Read(id)
	if err != nil {
		return emptyID, false
	}

	if archive == nil {
		return emptyID, false
	}

	// BundleInfoがあれば編集不可
	if archive.BundleInfo != nil && archive.BundleInfo.HostClass == bundleInfoWindowsHostClass {
		// Windows
		return emptyID, false
	}

	// SophosUTMであれば編集不可
	if archive.HasTag("pkg-sophosutm") || archive.IsSophosUTM() {
		return emptyID, false
	}

	for _, t := range allowDiskEditTags {
		if archive.HasTag(t) {
			// 対応OSインストール済みディスク
			return archive.ID, true
		}
	}

	// ここまできても判定できないならソースに投げる
	if archive.SourceDisk != nil && archive.SourceDisk.Availability != "discontinued" {
		return api.client.Disk.GetPublicArchiveIDFromAncestors(archive.SourceDisk.ID)
	}
	if archive.SourceArchive != nil && archive.SourceArchive.Availability != "discontinued" {
		return api.client.Archive.GetPublicArchiveIDFromAncestors(archive.SourceArchive.ID)
	}
	return emptyID, false

}

// FindLatestStableCentOS 安定版最新のCentOSパブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableCentOS() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableCentOSTags)
}

// FindLatestStableCentOS6 安定版最新のCentOS6パブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableCentOS6() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableCentOS6Tags)
}

// FindLatestStableDebian 安定版最新のDebianパブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableDebian() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableDebianTags)
}

// FindLatestStableUbuntu 安定版最新のUbuntuパブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableUbuntu() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableUbuntuTags)
}

// FindLatestStableVyOS 安定版最新のVyOSパブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableVyOS() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableVyOSTags)
}

// FindLatestStableCoreOS 安定版最新のCoreOSパブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableCoreOS() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableCoreOSTags)
}

// FindLatestStableRancherOS 安定版最新のRancherOSパブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableRancherOS() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableRancherOSTags)
}

// FindLatestStableKusanagi 安定版最新のKusanagiパブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableKusanagi() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableKusanagiTags)
}

// FindLatestStableSophosUTM 安定板最新のSophosUTMパブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableSophosUTM() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableSophosUTMTags)
}

// FindLatestStableFreeBSD 安定版最新のFreeBSDパブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableFreeBSD() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableFreeBSDTags)
}

// FindLatestStableWindows2012 安定版最新のWindows2012パブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableWindows2012() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableWindows2012Tags, map[string]interface{}{
		"Name": "Windows Server 2012 R2 Datacenter Edition",
	})
}

// FindLatestStableWindows2012RDS 安定版最新のWindows2012RDSパブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableWindows2012RDS() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableWindows2012RDSTags, map[string]interface{}{
		"Name": "Windows Server 2012 R2 for RDS",
	})
}

// FindLatestStableWindows2012RDSOffice 安定版最新のWindows2012RDS(Office)パブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableWindows2012RDSOffice() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableWindows2012RDSOfficeTags, map[string]interface{}{
		"Name": "Windows Server 2012 R2 for RDS(MS Office付)",
	})
}

// FindLatestStableWindows2016 安定版最新のWindows2016パブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableWindows2016() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableWindows2016Tags, map[string]interface{}{
		"Name": "Windows Server 2016 Datacenter Edition",
	})
}

// FindLatestStableWindows2016RDS 安定版最新のWindows2016RDSパブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableWindows2016RDS() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableWindows2016RDSTags, map[string]interface{}{
		"Name": "Windows Server 2016 for RDS",
	})
}

// FindLatestStableWindows2016RDSOffice 安定版最新のWindows2016RDS(Office)パブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableWindows2016RDSOffice() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableWindows2016RDSOfficeTags, map[string]interface{}{
		"Name": "Windows Server 2016 for RDS(MS Office付)",
	})
}

// FindLatestStableWindows2016SQLServerWeb 安定版最新のWindows2016 SQLServer(Web) パブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableWindows2016SQLServerWeb() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableWindows2016SQLServerWeb, map[string]interface{}{
		"Name": "Windows Server 2016 for MS SQL 2016(Web)",
	})
}

// FindLatestStableWindows2016SQLServerStandard 安定版最新のWindows2016 SQLServer(Standard) パブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableWindows2016SQLServerStandard() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableWindows2016SQLServerStandard, map[string]interface{}{
		"Name": "Windows Server 2016 for MS SQL 2016(Standard)",
	})
}

// FindLatestStableWindows2016SQLServer2017Standard 安定版最新のWindows2016 SQLServer2017(Standard) パブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableWindows2016SQLServer2017Standard() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableWindows2016SQLServer2017Standard, map[string]interface{}{
		"Name": "Windows Server 2016 for MS SQL 2017(Standard)",
	})
}

// FindLatestStableWindows2016SQLServerStandardAll 安定版最新のWindows2016 SQLServer(RDS+Office) パブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableWindows2016SQLServerStandardAll() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableWindows2016SQLServerStandardAll, map[string]interface{}{
		"Name": "Windows Server 2016 for MS SQL 2016(Std) with RDS / MS Office",
	})
}

// FindLatestStableWindows2016SQLServer2017StandardAll 安定版最新のWindows2016 SQLServer2017(RDS+Office) パブリックアーカイブを取得
func (api *ArchiveAPI) FindLatestStableWindows2016SQLServer2017StandardAll() (*sacloud.Archive, error) {
	return api.findByOSTags(archiveLatestStableWindows2016SQLServer2017StandardAll, map[string]interface{}{
		"Name": "Windows Server 2016 for MS SQL 2017(Std) with RDS / MS Office",
	})
}

// FindByOSType 指定のOS種別の安定版最新のパブリックアーカイブを取得
func (api *ArchiveAPI) FindByOSType(os ostype.ArchiveOSTypes) (*sacloud.Archive, error) {
	if f, ok := api.findFuncMapPerOSType[os]; ok {
		return f()
	}

	return nil, fmt.Errorf("OSType [%s] is invalid", os)
}

func (api *ArchiveAPI) findByOSTags(tags []string, filterMap ...map[string]interface{}) (*sacloud.Archive, error) {

	api.Reset().WithTags(tags)

	for _, filters := range filterMap {
		for key, filter := range filters {
			api.FilterMultiBy(key, filter)
		}
	}
	res, err := api.Find()
	if err != nil {
		return nil, fmt.Errorf("Archive [%s] error : %s", strings.Join(tags, ","), err)
	}

	if len(res.Archives) == 0 {
		return nil, fmt.Errorf("Archive [%s] Not Found", strings.Join(tags, ","))
	}

	return &res.Archives[0], nil

}