package api

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/sacloud/libsacloud/sacloud"
)

//HACK: さくらのAPI側仕様: Applianceの内容によってJSONフォーマットが異なるため
//      ロードバランサ/VPCルータそれぞれでリクエスト/レスポンスデータ型を定義する。

// SearchDatabaseResponse データベース検索レスポンス
type SearchDatabaseResponse struct {
	// Total 総件数
	Total int `json:",omitempty"`
	// From ページング開始位置
	From int `json:",omitempty"`
	// Count 件数
	Count int `json:",omitempty"`
	// Databases データベースリスト
	Databases []sacloud.Database `json:"Appliances,omitempty"`
}

type databaseRequest struct {
	Database *sacloud.Database      `json:"Appliance,omitempty"`
	From     int                    `json:",omitempty"`
	Count    int                    `json:",omitempty"`
	Sort     []string               `json:",omitempty"`
	Filter   map[string]interface{} `json:",omitempty"`
	Exclude  []string               `json:",omitempty"`
	Include  []string               `json:",omitempty"`
}

type databaseResponse struct {
	*sacloud.ResultFlagValue
	*sacloud.Database `json:"Appliance,omitempty"`
	// Success
	Success interface{} `json:",omitempty"` //HACK: さくらのAPI側仕様: 戻り値:Successがbool値へ変換できないためinterface{}で受ける
}

type databaseStatusResponse struct {
	*sacloud.ResultFlagValue
	Success   interface{} `json:",omitempty"` //HACK: さくらのAPI側仕様: 戻り値:Successがbool値へ変換できないためinterface{}
	Appliance *struct {
		SettingsResponse *sacloud.DatabaseStatus
	}
}

type databaseBackupResponse struct {
	Log  string `json:",omitempty"`
	IsOk bool   `json:"is_ok,omitempty"`
}

// DatabaseAPI データベースAPI
type DatabaseAPI struct {
	*baseAPI
}

// NewDatabaseAPI データベースAPI作成
func NewDatabaseAPI(client *Client) *DatabaseAPI {
	return &DatabaseAPI{
		&baseAPI{
			client: client,
			FuncGetResourceURL: func() string {
				return "appliance"
			},
			FuncBaseSearchCondition: func() *sacloud.Request {
				res := &sacloud.Request{}
				res.AddFilter("Class", "database")
				return res
			},
		},
	}
}

// Find 検索
func (api *DatabaseAPI) Find() (*SearchDatabaseResponse, error) {
	data, err := api.client.newRequest("GET", api.getResourceURL(), api.getSearchState())
	if err != nil {
		return nil, err
	}
	var res SearchDatabaseResponse
	if err := json.Unmarshal(data, &res); err != nil {
		return nil, err
	}
	return &res, nil
}

func (api *DatabaseAPI) request(f func(*databaseResponse) error) (*sacloud.Database, error) {
	res := &databaseResponse{}
	err := f(res)
	if err != nil {
		return nil, err
	}
	return res.Database, nil
}

func (api *DatabaseAPI) createRequest(value *sacloud.Database) *databaseResponse {
	return &databaseResponse{Database: value}
}

// New 新規作成用パラメーター作成
func (api *DatabaseAPI) New(values *sacloud.CreateDatabaseValue) *sacloud.Database {
	return sacloud.CreateNewDatabase(values)
}

// Create 新規作成
func (api *DatabaseAPI) Create(value *sacloud.Database) (*sacloud.Database, error) {
	return api.request(func(res *databaseResponse) error {
		return api.create(api.createRequest(value), res)
	})
}

// Read 読み取り
func (api *DatabaseAPI) Read(id int64) (*sacloud.Database, error) {
	return api.request(func(res *databaseResponse) error {
		return api.read(id, nil, res)
	})
}

// Status DBの設定/起動状態の取得
func (api *DatabaseAPI) Status(id int64) (*sacloud.DatabaseStatus, error) {
	var (
		method = "GET"
		uri    = fmt.Sprintf("%s/%d/status", api.getResourceURL(), id)
	)

	res := &databaseStatusResponse{}
	err := api.baseAPI.request(method, uri, nil, res)
	if err != nil {
		return nil, err
	}
	return res.Appliance.SettingsResponse, nil
}

// Backup バックアップ取得
func (api *DatabaseAPI) Backup(id int64) (string, error) {
	var (
		method = "POST"
		uri    = fmt.Sprintf("%s/%d/action/history", api.getResourceURL(), id)
	)

	body := map[string]interface{}{
		"Appliance": map[string]interface{}{
			"Settings": map[string]interface{}{
				"DBConf": map[string]interface{}{
					"backup": map[string]string{
						"availability": "discontinued",
					},
				},
			},
		},
	}

	res := &databaseBackupResponse{}
	err := api.baseAPI.request(method, uri, body, res)
	if err != nil {
		return "", err
	}
	return res.Log, nil
}

// DownloadLog ログ取得
func (api *DatabaseAPI) DownloadLog(id int64, logID string) (string, error) {
	var (
		method = "GET"
		uri    = fmt.Sprintf("%s/%d/download/log/%s", api.getResourceURL(), id, logID)
	)

	res := &databaseBackupResponse{}
	err := api.baseAPI.request(method, uri, nil, res)
	if err != nil {
		return "", err
	}
	return res.Log, nil
}

// Restore バックアップからの復元
func (api *DatabaseAPI) Restore(id int64, backupID string) (string, error) {
	var (
		method = "POST"
		uri    = fmt.Sprintf("%s/%d/action/history/%s", api.getResourceURL(), id, backupID)
	)

	body := map[string]interface{}{
		"Appliance": map[string]interface{}{},
	}

	res := &databaseBackupResponse{}
	err := api.baseAPI.request(method, uri, body, res)
	if err != nil {
		return "", err
	}
	return res.Log, nil
}

// DeleteBackup バックアップの削除
func (api *DatabaseAPI) DeleteBackup(id int64, backupID string) (string, error) {
	var (
		method = "DELETE"
		uri    = fmt.Sprintf("%s/%d/action/history/%s", api.getResourceURL(), id, backupID)
	)

	body := map[string]interface{}{
		"Appliance": map[string]interface{}{},
	}

	res := &databaseBackupResponse{}
	err := api.baseAPI.request(method, uri, body, res)
	if err != nil {
		return "", err
	}
	return res.Log, nil
}

// HistoryLock バックアップ削除ロック
func (api *DatabaseAPI) HistoryLock(id int64, backupID string) (string, error) {
	var (
		method = "PUT"
		uri    = fmt.Sprintf("%s/%d/action/history-lock/%s", api.getResourceURL(), id, backupID)
	)

	body := map[string]interface{}{
		"Appliance": map[string]interface{}{},
	}

	res := &databaseBackupResponse{}
	err := api.baseAPI.request(method, uri, body, res)
	if err != nil {
		return "", err
	}
	return res.Log, nil
}

// HistoryUnlock バックアップ削除アンロック
func (api *DatabaseAPI) HistoryUnlock(id int64, backupID string) (string, error) {
	var (
		method = "DELETE"
		uri    = fmt.Sprintf("%s/%d/action/history-lock/%s", api.getResourceURL(), id, backupID)
	)

	body := map[string]interface{}{
		"Appliance": map[string]interface{}{},
	}

	res := &databaseBackupResponse{}
	err := api.baseAPI.request(method, uri, body, res)
	if err != nil {
		return "", err
	}
	return res.Log, nil
}

// Update 更新
func (api *DatabaseAPI) Update(id int64, value *sacloud.Database) (*sacloud.Database, error) {
	return api.request(func(res *databaseResponse) error {
		return api.update(id, api.createRequest(value), res)
	})
}

// UpdateSetting 設定更新
func (api *DatabaseAPI) UpdateSetting(id int64, value *sacloud.Database) (*sacloud.Database, error) {
	req := &sacloud.Database{
		// Settings
		Settings: value.Settings,
	}
	return api.request(func(res *databaseResponse) error {
		return api.update(id, api.createRequest(req), res)
	})
}

// Delete 削除
func (api *DatabaseAPI) Delete(id int64) (*sacloud.Database, error) {
	return api.request(func(res *databaseResponse) error {
		return api.delete(id, nil, res)
	})
}

// Config 設定変更の反映
func (api *DatabaseAPI) Config(id int64) (bool, error) {
	var (
		method = "PUT"
		uri    = fmt.Sprintf("%s/%d/config", api.getResourceURL(), id)
	)
	return api.modify(method, uri, nil)
}

// IsUp 起動しているか判定
func (api *DatabaseAPI) IsUp(id int64) (bool, error) {
	lb, err := api.Read(id)
	if err != nil {
		return false, err
	}
	return lb.Instance.IsUp(), nil
}

// IsDown ダウンしているか判定
func (api *DatabaseAPI) IsDown(id int64) (bool, error) {
	lb, err := api.Read(id)
	if err != nil {
		return false, err
	}
	return lb.Instance.IsDown(), nil
}

// IsDatabaseRunning データベースプロセスが起動しているか判定
func (api *DatabaseAPI) IsDatabaseRunning(id int64) (bool, error) {
	db, err := api.Status(id)
	if err != nil {
		return false, err
	}
	return db.IsUp(), nil

}

// Boot 起動
func (api *DatabaseAPI) Boot(id int64) (bool, error) {
	var (
		method = "PUT"
		uri    = fmt.Sprintf("%s/%d/power", api.getResourceURL(), id)
	)
	return api.modify(method, uri, nil)
}

// Shutdown シャットダウン(graceful)
func (api *DatabaseAPI) Shutdown(id int64) (bool, error) {
	var (
		method = "DELETE"
		uri    = fmt.Sprintf("%s/%d/power", api.getResourceURL(), id)
	)

	return api.modify(method, uri, nil)
}

// Stop シャットダウン(force)
func (api *DatabaseAPI) Stop(id int64) (bool, error) {
	var (
		method = "DELETE"
		uri    = fmt.Sprintf("%s/%d/power", api.getResourceURL(), id)
	)

	return api.modify(method, uri, map[string]bool{"Force": true})
}

// RebootForce 再起動
func (api *DatabaseAPI) RebootForce(id int64) (bool, error) {
	var (
		method = "PUT"
		uri    = fmt.Sprintf("%s/%d/reset", api.getResourceURL(), id)
	)

	return api.modify(method, uri, nil)
}

// ResetForce リセット
func (api *DatabaseAPI) ResetForce(id int64, recycleProcess bool) (bool, error) {
	var (
		method = "PUT"
		uri    = fmt.Sprintf("%s/%d/reset", api.getResourceURL(), id)
	)

	return api.modify(method, uri, map[string]bool{"RecycleProcess": recycleProcess})
}

// SleepUntilUp 起動するまで待機
func (api *DatabaseAPI) SleepUntilUp(id int64, timeout time.Duration) error {
	handler := waitingForUpFunc(func() (hasUpDown, error) {
		return api.Read(id)
	}, 0)
	return blockingPoll(handler, timeout)
}

// SleepUntilDatabaseRunning 起動するまで待機
func (api *DatabaseAPI) SleepUntilDatabaseRunning(id int64, timeout time.Duration, maxRetry int) error {
	handler := waitingForUpFunc(func() (hasUpDown, error) {
		return api.Read(id)
	}, maxRetry)
	return blockingPoll(handler, timeout)
}

// SleepUntilDown ダウンするまで待機
func (api *DatabaseAPI) SleepUntilDown(id int64, timeout time.Duration) error {
	handler := waitingForDownFunc(func() (hasUpDown, error) {
		return api.Read(id)
	}, 0)
	return blockingPoll(handler, timeout)
}

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

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

// MonitorCPU CPUアクティビティーモニター取得
func (api *DatabaseAPI) MonitorCPU(id int64, body *sacloud.ResourceMonitorRequest) (*sacloud.MonitorValues, error) {
	return api.baseAPI.applianceMonitorBy(id, "cpu", 0, body)
}

// MonitorDatabase データーベース固有項目アクティビティモニター取得
func (api *DatabaseAPI) MonitorDatabase(id int64, body *sacloud.ResourceMonitorRequest) (*sacloud.MonitorValues, error) {
	return api.baseAPI.applianceMonitorBy(id, "database", 0, body)
}

// MonitorInterface NICアクティビティーモニター取得
func (api *DatabaseAPI) MonitorInterface(id int64, body *sacloud.ResourceMonitorRequest) (*sacloud.MonitorValues, error) {
	return api.baseAPI.applianceMonitorBy(id, "interface", 0, body)
}

// MonitorSystemDisk システムディスクアクティビティーモニター取得
func (api *DatabaseAPI) MonitorSystemDisk(id int64, body *sacloud.ResourceMonitorRequest) (*sacloud.MonitorValues, error) {
	return api.baseAPI.applianceMonitorBy(id, "disk", 1, body)
}

// MonitorBackupDisk バックアップディスクアクティビティーモニター取得
func (api *DatabaseAPI) MonitorBackupDisk(id int64, body *sacloud.ResourceMonitorRequest) (*sacloud.MonitorValues, error) {
	return api.baseAPI.applianceMonitorBy(id, "disk", 2, body)
}
