package api

import (
	"encoding/json"
	"fmt"
	"net/url"

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

type baseAPI struct {
	client                  *Client
	FuncGetResourceURL      func() string
	FuncBaseSearchCondition func() *sacloud.Request
	state                   *sacloud.Request
	apiRootSuffix           string
}

var (
	sakuraCloudAPIRootSuffix    = "api/cloud/1.1"
	sakuraBillingAPIRootSuffix  = "api/system/1.0"
	sakuraWebAccelAPIRootSuffix = "api/webaccel/1.0"
)

func (api *baseAPI) getResourceURL() string {

	suffix := api.apiRootSuffix
	//デフォルト : クラウドAPI
	if suffix == "" {
		suffix = sakuraCloudAPIRootSuffix
	}

	url := ""
	if api.FuncGetResourceURL != nil {
		url = api.FuncGetResourceURL()
	}

	if suffix == "" {
		return url
	}
	if url == "" {
		return suffix
	}

	return fmt.Sprintf("%s/%s", suffix, url)
}

func (api *baseAPI) getSearchState() *sacloud.Request {
	if api.state == nil {
		api.reset()
	}
	return api.state
}
func (api *baseAPI) sortBy(key string, reverse bool) *baseAPI {
	return api.setStateValue(func(state *sacloud.Request) {
		if state.Sort == nil {
			state.Sort = []string{}
		}

		col := key
		if reverse {
			col = "-" + col
		}
		state.Sort = append(state.Sort, col)

	})

}

func (api *baseAPI) reset() *baseAPI {
	if api.FuncBaseSearchCondition == nil {
		api.state = &sacloud.Request{}
	} else {
		api.state = api.FuncBaseSearchCondition()
	}
	return api
}

func (api *baseAPI) setStateValue(setFunc func(*sacloud.Request)) *baseAPI {
	state := api.getSearchState()
	setFunc(state)
	return api

}

func (api *baseAPI) offset(offset int) *baseAPI {
	return api.setStateValue(func(state *sacloud.Request) {
		state.From = offset
	})
}

func (api *baseAPI) limit(limit int) *baseAPI {
	return api.setStateValue(func(state *sacloud.Request) {
		state.Count = limit
	})
}

func (api *baseAPI) include(key string) *baseAPI {
	return api.setStateValue(func(state *sacloud.Request) {
		if state.Include == nil {
			state.Include = []string{}
		}
		state.Include = append(state.Include, key)
	})
}

func (api *baseAPI) exclude(key string) *baseAPI {
	return api.setStateValue(func(state *sacloud.Request) {
		if state.Exclude == nil {
			state.Exclude = []string{}
		}
		state.Exclude = append(state.Exclude, key)
	})
}

func (api *baseAPI) filterBy(key string, value interface{}, multiple bool) *baseAPI {
	return api.setStateValue(func(state *sacloud.Request) {

		//HACK さくらのクラウド側でqueryStringでの+エスケープに対応していないため、
		// %20にエスケープされるurl.Pathを利用する。
		// http://qiita.com/shibukawa/items/c0730092371c0e243f62
		if strValue, ok := value.(string); ok {
			u := &url.URL{Path: strValue}
			value = u.String()
		}

		if state.Filter == nil {
			state.Filter = map[string]interface{}{}
		}
		if multiple {
			if state.Filter[key] == nil {
				state.Filter[key] = []interface{}{}
			}

			state.Filter[key] = append(state.Filter[key].([]interface{}), value)
		} else {
			// どちらもstring型の場合はスペース区切りで繋げる
			if f, ok := state.Filter[key]; ok {
				if s, ok := f.(string); ok && s != "" {
					if v, ok := value.(string); ok {
						state.Filter[key] = fmt.Sprintf("%s %s", s, v)
						return
					}
				}
			}
			state.Filter[key] = value

		}
	})
}

func (api *baseAPI) withNameLike(name string) *baseAPI {
	return api.filterBy("Name", name, false)
}

func (api *baseAPI) withTag(tag string) *baseAPI {
	return api.filterBy("Tags.Name", tag, false)
}

func (api *baseAPI) withTags(tags []string) *baseAPI {
	return api.filterBy("Tags.Name", tags, false)
}

func (api *baseAPI) sortByName(reverse bool) *baseAPI {
	return api.sortBy("Name", reverse)
}

func (api *baseAPI) Find() (*sacloud.SearchResponse, error) {

	data, err := api.client.newRequest("GET", api.getResourceURL(), api.getSearchState())
	if err != nil {
		return nil, err
	}
	var res sacloud.SearchResponse
	if err := json.Unmarshal(data, &res); err != nil {
		return nil, err
	}
	return &res, nil
}

func (api *baseAPI) request(method string, uri string, body interface{}, res interface{}) error {
	data, err := api.client.newRequest(method, uri, body)
	if err != nil {
		return err
	}

	if res != nil {
		if err := json.Unmarshal(data, &res); err != nil {
			return err
		}
	}
	return nil
}

func (api *baseAPI) create(body interface{}, res interface{}) error {
	var (
		method = "POST"
		uri    = api.getResourceURL()
	)

	return api.request(method, uri, body, res)
}

func (api *baseAPI) read(id int64, body interface{}, res interface{}) error {
	var (
		method = "GET"
		uri    = fmt.Sprintf("%s/%d", api.getResourceURL(), id)
	)

	return api.request(method, uri, body, res)
}

func (api *baseAPI) update(id int64, body interface{}, res interface{}) error {
	var (
		method = "PUT"
		uri    = fmt.Sprintf("%s/%d", api.getResourceURL(), id)
	)
	return api.request(method, uri, body, res)
}

func (api *baseAPI) delete(id int64, body interface{}, res interface{}) error {
	var (
		method = "DELETE"
		uri    = fmt.Sprintf("%s/%d", api.getResourceURL(), id)
	)
	return api.request(method, uri, body, res)
}

func (api *baseAPI) modify(method string, uri string, body interface{}) (bool, error) {
	res := &sacloud.ResultFlagValue{}
	err := api.request(method, uri, body, res)
	if err != nil {
		return false, err
	}
	return res.IsOk, nil
}

func (api *baseAPI) action(method string, uri string, body interface{}, res interface{}) (bool, error) {
	err := api.request(method, uri, body, res)
	if err != nil {
		return false, err
	}
	return true, nil
}

func (api *baseAPI) monitor(id int64, body *sacloud.ResourceMonitorRequest) (*sacloud.MonitorValues, error) {
	var (
		method = "GET"
		uri    = fmt.Sprintf("%s/%d/monitor", api.getResourceURL(), id)
	)
	res := &sacloud.ResourceMonitorResponse{}
	err := api.request(method, uri, body, res)
	if err != nil {
		return nil, err
	}
	return res.Data, nil
}

func (api *baseAPI) applianceMonitorBy(id int64, target string, nicIndex int, body *sacloud.ResourceMonitorRequest) (*sacloud.MonitorValues, error) {
	var (
		method = "GET"
		uri    = fmt.Sprintf("%s/%d/%s/%d/monitor", api.getResourceURL(), id, target, nicIndex)
	)
	if nicIndex == 0 {
		uri = fmt.Sprintf("%s/%d/%s/monitor", api.getResourceURL(), id, target)
	}

	res := &sacloud.ResourceMonitorResponse{}
	err := api.request(method, uri, body, res)
	if err != nil {
		return nil, err
	}
	return res.Data, nil
}

func (api *baseAPI) NewResourceMonitorRequest() *sacloud.ResourceMonitorRequest {
	return &sacloud.ResourceMonitorRequest{}
}
