package api

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

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

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

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

type vpcRouterRequest struct {
	VPCRouter *sacloud.VPCRouter     `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 vpcRouterResponse struct {
	*sacloud.ResultFlagValue
	*sacloud.VPCRouter `json:"Appliance,omitempty"`
	Success            interface{} `json:",omitempty"` //HACK: さくらのAPI側仕様: 戻り値:Successがbool値へ変換できないためinterface{}
}

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

type vpcRouterS2sConnInfoResponse struct {
	*sacloud.ResultFlagValue
	*sacloud.SiteToSiteConnectionInfo
	Name string
}

// VPCRouterAPI VPCルーターAPI
type VPCRouterAPI struct {
	*baseAPI
}

// NewVPCRouterAPI VPCルーターAPI作成
func NewVPCRouterAPI(client *Client) *VPCRouterAPI {
	return &VPCRouterAPI{
		&baseAPI{
			client: client,
			FuncGetResourceURL: func() string {
				return "appliance"
			},
			FuncBaseSearchCondition: func() *sacloud.Request {
				res := &sacloud.Request{}
				res.AddFilter("Class", "vpcrouter")
				return res
			},
		},
	}
}

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

func (api *VPCRouterAPI) request(f func(*vpcRouterResponse) error) (*sacloud.VPCRouter, error) {
	res := &vpcRouterResponse{}
	err := f(res)
	if err != nil {
		return nil, err
	}
	return res.VPCRouter, nil
}

func (api *VPCRouterAPI) createRequest(value *sacloud.VPCRouter) *vpcRouterResponse {
	return &vpcRouterResponse{VPCRouter: value}
}

// New 新規作成用パラメーター作成
func (api *VPCRouterAPI) New() *sacloud.VPCRouter {
	return sacloud.CreateNewVPCRouter()
}

// Create 新規作成
func (api *VPCRouterAPI) Create(value *sacloud.VPCRouter) (*sacloud.VPCRouter, error) {
	return api.request(func(res *vpcRouterResponse) error {
		return api.create(api.createRequest(value), res)
	})
}

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

// Update 更新
func (api *VPCRouterAPI) Update(id int64, value *sacloud.VPCRouter) (*sacloud.VPCRouter, error) {
	return api.request(func(res *vpcRouterResponse) error {
		return api.update(id, api.createRequest(value), res)
	})
}

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

// Delete 削除
func (api *VPCRouterAPI) Delete(id int64) (*sacloud.VPCRouter, error) {
	return api.request(func(res *vpcRouterResponse) error {
		return api.delete(id, nil, res)
	})
}

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

// ConnectToSwitch 指定のインデックス位置のNICをスイッチへ接続
func (api *VPCRouterAPI) ConnectToSwitch(id int64, switchID int64, nicIndex int) (bool, error) {
	var (
		method = "PUT"
		uri    = fmt.Sprintf("%s/%d/interface/%d/to/switch/%d", api.getResourceURL(), id, nicIndex, switchID)
	)
	return api.modify(method, uri, nil)
}

// DisconnectFromSwitch 指定のインデックス位置のNICをスイッチから切断
func (api *VPCRouterAPI) DisconnectFromSwitch(id int64, nicIndex int) (bool, error) {
	var (
		method = "DELETE"
		uri    = fmt.Sprintf("%s/%d/interface/%d/to/switch", api.getResourceURL(), id, nicIndex)
	)
	return api.modify(method, uri, nil)
}

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

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

// Boot 起動
func (api *VPCRouterAPI) 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 *VPCRouterAPI) 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 *VPCRouterAPI) 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 *VPCRouterAPI) RebootForce(id int64) (bool, error) {
	var (
		method = "PUT"
		uri    = fmt.Sprintf("%s/%d/reset", api.getResourceURL(), id)
	)

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

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

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

// SleepWhileCopying コピー終了まで待機
//
// maxRetry: リクエストタイミングによって、コピー完了までの間に404エラーとなる場合がある。
// 通常そのまま待てばコピー完了するため、404エラーが発生してもmaxRetryで指定した回数分は待機する。
func (api *VPCRouterAPI) 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 *VPCRouterAPI) 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)
}

// AddStandardInterface スタンダードプランでのインターフェース追加
func (api *VPCRouterAPI) AddStandardInterface(routerID int64, switchID int64, ipaddress string, maskLen int) (*sacloud.VPCRouter, error) {
	return api.addInterface(routerID, switchID, &sacloud.VPCRouterInterface{
		IPAddress:        []string{ipaddress},
		NetworkMaskLen:   maskLen,
		VirtualIPAddress: "",
	})
}

// AddPremiumInterface プレミアムプランでのインターフェース追加
func (api *VPCRouterAPI) AddPremiumInterface(routerID int64, switchID int64, ipaddresses []string, maskLen int, virtualIP string) (*sacloud.VPCRouter, error) {
	return api.addInterface(routerID, switchID, &sacloud.VPCRouterInterface{
		IPAddress:        ipaddresses,
		NetworkMaskLen:   maskLen,
		VirtualIPAddress: virtualIP,
	})
}

func (api *VPCRouterAPI) addInterface(routerID int64, switchID int64, routerNIC *sacloud.VPCRouterInterface) (*sacloud.VPCRouter, error) {
	router, err := api.Read(routerID)
	if err != nil {
		return nil, err
	}
	req := &sacloud.VPCRouter{Settings: &sacloud.VPCRouterSettings{}}

	if router.Settings == nil {
		req.Settings = &sacloud.VPCRouterSettings{
			Router: &sacloud.VPCRouterSetting{
				Interfaces: []*sacloud.VPCRouterInterface{nil},
			},
		}
	} else {
		req.Settings.Router = router.Settings.Router
	}

	index := len(req.Settings.Router.Interfaces) // add to last
	return api.addInterfaceAt(routerID, switchID, routerNIC, index)
}

// AddStandardInterfaceAt スタンダードプランでの指定位置へのインターフェース追加
func (api *VPCRouterAPI) AddStandardInterfaceAt(routerID int64, switchID int64, ipaddress string, maskLen int, index int) (*sacloud.VPCRouter, error) {
	return api.addInterfaceAt(routerID, switchID, &sacloud.VPCRouterInterface{
		IPAddress:        []string{ipaddress},
		NetworkMaskLen:   maskLen,
		VirtualIPAddress: "",
	}, index)
}

// AddPremiumInterfaceAt プレミアムプランでの指定位置へのインターフェース追加
func (api *VPCRouterAPI) AddPremiumInterfaceAt(routerID int64, switchID int64, ipaddresses []string, maskLen int, virtualIP string, index int) (*sacloud.VPCRouter, error) {
	return api.addInterfaceAt(routerID, switchID, &sacloud.VPCRouterInterface{
		IPAddress:        ipaddresses,
		NetworkMaskLen:   maskLen,
		VirtualIPAddress: virtualIP,
	}, index)
}

func (api *VPCRouterAPI) addInterfaceAt(routerID int64, switchID int64, routerNIC *sacloud.VPCRouterInterface, index int) (*sacloud.VPCRouter, error) {
	router, err := api.Read(routerID)
	if err != nil {
		return nil, err
	}

	req := &sacloud.VPCRouter{Settings: &sacloud.VPCRouterSettings{}}

	if router.Settings == nil {
		req.Settings = &sacloud.VPCRouterSettings{
			Router: &sacloud.VPCRouterSetting{
				Interfaces: []*sacloud.VPCRouterInterface{nil},
			},
		}
	} else {
		req.Settings.Router = router.Settings.Router
	}

	//connect to switch
	_, err = api.ConnectToSwitch(routerID, switchID, index)
	if err != nil {
		return nil, err
	}

	for i := 0; i < index; i++ {
		if len(req.Settings.Router.Interfaces) < index {
			req.Settings.Router.Interfaces = append(req.Settings.Router.Interfaces, nil)
		}
	}

	if len(req.Settings.Router.Interfaces) < index+1 {
		req.Settings.Router.Interfaces = append(req.Settings.Router.Interfaces, routerNIC)
	} else {
		req.Settings.Router.Interfaces[index] = routerNIC
	}

	res, err := api.UpdateSetting(routerID, req)
	if err != nil {
		return nil, err
	}

	return res, nil

}

// DeleteInterfaceAt 指定位置のインターフェース削除
func (api *VPCRouterAPI) DeleteInterfaceAt(routerID int64, index int) (*sacloud.VPCRouter, error) {
	router, err := api.Read(routerID)
	if err != nil {
		return nil, err
	}

	req := &sacloud.VPCRouter{Settings: &sacloud.VPCRouterSettings{}}

	if router.Settings == nil {
		req.Settings = &sacloud.VPCRouterSettings{
			// Router
			Router: &sacloud.VPCRouterSetting{
				// Interfaces
				Interfaces: []*sacloud.VPCRouterInterface{nil},
			},
		}
	} else {
		req.Settings.Router = router.Settings.Router
	}

	//disconnect to switch
	_, err = api.DisconnectFromSwitch(routerID, index)
	if err != nil {
		return nil, err
	}

	if index < len(req.Settings.Router.Interfaces) {
		req.Settings.Router.Interfaces[index] = nil
	}

	res, err := api.UpdateSetting(routerID, req)
	if err != nil {
		return nil, err
	}

	return res, nil

}

// MonitorBy 指定位置のインターフェースのアクティビティーモニター取得
func (api *VPCRouterAPI) MonitorBy(id int64, nicIndex int, body *sacloud.ResourceMonitorRequest) (*sacloud.MonitorValues, error) {
	return api.baseAPI.applianceMonitorBy(id, "interface", nicIndex, body)
}

// Status ログなどのステータス情報 取得
func (api *VPCRouterAPI) Status(id int64) (*sacloud.VPCRouterStatus, error) {
	var (
		method = "GET"
		uri    = fmt.Sprintf("%s/%d/status", api.getResourceURL(), id)
		res    = &vpcRouterStatusResponse{}
	)
	err := api.baseAPI.request(method, uri, nil, res)
	if err != nil {
		return nil, err
	}
	return res.VPCRouterStatus, nil
}

// SiteToSiteConnectionDetails サイト間VPN接続情報を取得
func (api *VPCRouterAPI) SiteToSiteConnectionDetails(id int64) (*sacloud.SiteToSiteConnectionInfo, error) {
	var (
		method = "GET"
		uri    = fmt.Sprintf("%s/%d/vpcrouter/sitetosite/connectiondetails", api.getResourceURL(), id)
		res    = &vpcRouterS2sConnInfoResponse{}
	)
	err := api.baseAPI.request(method, uri, nil, res)
	if err != nil {
		return nil, err
	}
	return res.SiteToSiteConnectionInfo, nil
}
