package api

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
	"time"

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

var (
	// SakuraCloudAPIRoot APIリクエスト送信先ルートURL(末尾にスラッシュを含まない)
	SakuraCloudAPIRoot = "https://secure.sakura.ad.jp/cloud/zone"
)

// Client APIクライアント
type Client struct {
	// AccessToken アクセストークン
	AccessToken string
	// AccessTokenSecret アクセストークンシークレット
	AccessTokenSecret string
	// Zone 対象ゾーン
	Zone string
	*API
	// TraceMode トレースモード
	TraceMode bool
	// DefaultTimeoutDuration デフォルトタイムアウト間隔
	DefaultTimeoutDuration time.Duration
	// ユーザーエージェント
	UserAgent string
	// Accept-Language
	AcceptLanguage string
	// リクエストパラメーター トレーサー
	RequestTracer io.Writer
	// レスポンス トレーサー
	ResponseTracer io.Writer
	// 503エラー時のリトライ回数
	RetryMax int
	// 503エラー時のリトライ待ち時間
	RetryInterval time.Duration
}

// NewClient APIクライアント作成
func NewClient(token, tokenSecret, zone string) *Client {
	c := &Client{
		AccessToken:            token,
		AccessTokenSecret:      tokenSecret,
		Zone:                   zone,
		TraceMode:              false,
		DefaultTimeoutDuration: 20 * time.Minute,
		UserAgent:              fmt.Sprintf("libsacloud/%s", libsacloud.Version),
		AcceptLanguage:         "",
		RetryMax:               0,
		RetryInterval:          5 * time.Second,
	}
	c.API = newAPI(c)
	return c
}

// Clone APIクライアント クローン作成
func (c *Client) Clone() *Client {
	n := &Client{
		AccessToken:            c.AccessToken,
		AccessTokenSecret:      c.AccessTokenSecret,
		Zone:                   c.Zone,
		TraceMode:              c.TraceMode,
		DefaultTimeoutDuration: c.DefaultTimeoutDuration,
		UserAgent:              c.UserAgent,
		AcceptLanguage:         c.AcceptLanguage,
		RetryMax:               c.RetryMax,
		RetryInterval:          c.RetryInterval,
	}
	n.API = newAPI(n)
	return n
}

func (c *Client) getEndpoint() string {
	return fmt.Sprintf("%s/%s", SakuraCloudAPIRoot, c.Zone)
}

func (c *Client) isOkStatus(code int) bool {
	codes := map[int]bool{
		200: true,
		201: true,
		202: true,
		204: true,
		305: false,
		400: false,
		401: false,
		403: false,
		404: false,
		405: false,
		406: false,
		408: false,
		409: false,
		411: false,
		413: false,
		415: false,
		500: false,
		503: false,
	}
	return codes[code]
}

func (c *Client) newRequest(method, uri string, body interface{}) ([]byte, error) {
	var (
		client = &retryableHTTPClient{
			retryMax:      c.RetryMax,
			retryInterval: c.RetryInterval,
		}
		err error
		req *request
	)
	var url = uri
	if !strings.HasPrefix(url, "https://") {
		url = fmt.Sprintf("%s/%s", c.getEndpoint(), uri)
	}

	if body != nil {
		var bodyJSON []byte
		bodyJSON, err = json.Marshal(body)
		if err != nil {
			return nil, err
		}
		if method == "GET" {
			url = fmt.Sprintf("%s?%s", url, bytes.NewBuffer(bodyJSON))
			req, err = newRequest(method, url, nil)
		} else {
			req, err = newRequest(method, url, bytes.NewReader(bodyJSON))
		}
		b, _ := json.MarshalIndent(body, "", "\t")
		if c.TraceMode {
			log.Printf("[libsacloud:Client#request] method : %#v , url : %s , \nbody : %s", method, url, b)
		}
		if c.RequestTracer != nil {
			c.RequestTracer.Write(b)
		}
	} else {
		req, err = newRequest(method, url, nil)
		if c.TraceMode {
			log.Printf("[libsacloud:Client#request] method : %#v , url : %s ", method, url)
		}
	}

	if err != nil {
		return nil, fmt.Errorf("Error with request: %v - %q", url, err)
	}

	req.SetBasicAuth(c.AccessToken, c.AccessTokenSecret)
	req.Header.Add("X-Sakura-Bigint-As-Int", "1") //Use BigInt on resource ids.
	//if c.TraceMode {
	//	req.Header.Add("X-Sakura-API-Beautify", "1") // format response-JSON
	//}
	req.Header.Add("User-Agent", c.UserAgent)
	if c.AcceptLanguage != "" {
		req.Header.Add("Accept-Language", c.AcceptLanguage)
	}
	req.Method = method

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	data, err := ioutil.ReadAll(resp.Body)

	v := &map[string]interface{}{}
	json.Unmarshal(data, v)
	b, _ := json.MarshalIndent(v, "", "\t")
	if c.ResponseTracer != nil {
		c.ResponseTracer.Write(b)
	}

	if c.TraceMode {
		log.Printf("[libsacloud:Client#response] : %s", b)
	}
	if !c.isOkStatus(resp.StatusCode) {

		errResponse := &sacloud.ResultErrorValue{}
		err := json.Unmarshal(data, errResponse)

		if err != nil {
			return nil, fmt.Errorf("Error in response: %s", string(data))
		}
		return nil, NewError(resp.StatusCode, errResponse)

	}
	if err != nil {
		return nil, err
	}

	return data, nil
}

type lenReader interface {
	Len() int
}

type request struct {
	// body is a seekable reader over the request body payload. This is
	// used to rewind the request data in between retries.
	body io.ReadSeeker

	// Embed an HTTP request directly. This makes a *Request act exactly
	// like an *http.Request so that all meta methods are supported.
	*http.Request
}

func newRequest(method, url string, body io.ReadSeeker) (*request, error) {
	var rcBody io.ReadCloser
	if body != nil {
		rcBody = ioutil.NopCloser(body)
	}

	httpReq, err := http.NewRequest(method, url, rcBody)
	if err != nil {
		return nil, err
	}

	if lr, ok := body.(lenReader); ok {
		httpReq.ContentLength = int64(lr.Len())
	}

	return &request{body, httpReq}, nil
}

type retryableHTTPClient struct {
	http.Client
	retryInterval time.Duration
	retryMax      int
}

func (c *retryableHTTPClient) Do(req *request) (*http.Response, error) {
	for i := 0; ; i++ {

		if req.body != nil {
			if _, err := req.body.Seek(0, 0); err != nil {
				return nil, fmt.Errorf("failed to seek body: %v", err)
			}
		}

		res, err := c.Client.Do(req.Request)
		if res != nil && res.StatusCode != 503 {
			return res, err
		}
		if res != nil && res.Body != nil {
			res.Body.Close()
		}

		if err != nil {
			return res, err
		}

		remain := c.retryMax - i
		if remain == 0 {
			break
		}
		time.Sleep(c.retryInterval)
	}

	return nil, fmt.Errorf("%s %s giving up after %d attempts",
		req.Method, req.URL, c.retryMax+1)
}

// API libsacloudでサポートしているAPI群
type API struct {
	AuthStatus    *AuthStatusAPI    // 認証状態API
	AutoBackup    *AutoBackupAPI    // 自動バックアップAPI
	Archive       *ArchiveAPI       // アーカイブAPI
	Bill          *BillAPI          // 請求情報API
	Bridge        *BridgeAPI        // ブリッジAPi
	CDROM         *CDROMAPI         // ISOイメージAPI
	Database      *DatabaseAPI      // データベースAPI
	Disk          *DiskAPI          // ディスクAPI
	DNS           *DNSAPI           // DNS API
	Facility      *FacilityAPI      // ファシリティAPI
	GSLB          *GSLBAPI          // GSLB API
	Icon          *IconAPI          // アイコンAPI
	Interface     *InterfaceAPI     // インターフェースAPI
	Internet      *InternetAPI      // ルーターAPI
	IPAddress     *IPAddressAPI     // IPアドレスAPI
	IPv6Addr      *IPv6AddrAPI      // IPv6アドレスAPI
	IPv6Net       *IPv6NetAPI       // IPv6ネットワークAPI
	License       *LicenseAPI       // ライセンスAPI
	LoadBalancer  *LoadBalancerAPI  // ロードバランサーAPI
	MobileGateway *MobileGatewayAPI // モバイルゲートウェイAPI
	NewsFeed      *NewsFeedAPI      // フィード(障害/メンテナンス情報)API
	NFS           *NFSAPI           // NFS API
	Note          *NoteAPI          // スタートアップスクリプトAPI
	PacketFilter  *PacketFilterAPI  // パケットフィルタAPI
	PrivateHost   *PrivateHostAPI   // 専有ホストAPI
	Product       *ProductAPI       // 製品情報API
	Server        *ServerAPI        // サーバーAPI
	SIM           *SIMAPI           // SIM API
	SimpleMonitor *SimpleMonitorAPI // シンプル監視API
	SSHKey        *SSHKeyAPI        // 公開鍵API
	Subnet        *SubnetAPI        // IPv4ネットワークAPI
	Switch        *SwitchAPI        // スイッチAPI
	VPCRouter     *VPCRouterAPI     // VPCルーターAPI
	WebAccel      *WebAccelAPI      // ウェブアクセラレータAPI
}

// GetAuthStatusAPI  認証状態API取得
func (api *API) GetAuthStatusAPI() *AuthStatusAPI {
	return api.AuthStatus
}

// GetAutoBackupAPI 自動バックアップAPI取得
func (api *API) GetAutoBackupAPI() *AutoBackupAPI {
	return api.AutoBackup
}

// GetArchiveAPI アーカイブAPI取得
func (api *API) GetArchiveAPI() *ArchiveAPI {
	return api.Archive
}

// GetBillAPI 請求情報API取得
func (api *API) GetBillAPI() *BillAPI {
	return api.Bill
}

// GetBridgeAPI ブリッジAPI取得
func (api *API) GetBridgeAPI() *BridgeAPI {
	return api.Bridge
}

// GetCDROMAPI ISOイメージAPI取得
func (api *API) GetCDROMAPI() *CDROMAPI {
	return api.CDROM
}

// GetDatabaseAPI データベースAPI取得
func (api *API) GetDatabaseAPI() *DatabaseAPI {
	return api.Database
}

// GetDiskAPI  ディスクAPI取得
func (api *API) GetDiskAPI() *DiskAPI {
	return api.Disk
}

// GetDNSAPI  DNSAPI取得
func (api *API) GetDNSAPI() *DNSAPI {
	return api.DNS
}

// GetRegionAPI リージョンAPI取得
func (api *API) GetRegionAPI() *RegionAPI {
	return api.Facility.GetRegionAPI()
}

// GetZoneAPI  ゾーンAPI取得
func (api *API) GetZoneAPI() *ZoneAPI {
	return api.Facility.GetZoneAPI()
}

// GetGSLBAPI  GSLB API取得
func (api *API) GetGSLBAPI() *GSLBAPI {
	return api.GSLB
}

// GetIconAPI  アイコンAPI取得
func (api *API) GetIconAPI() *IconAPI {
	return api.Icon
}

// GetInterfaceAPI インターフェースAPI取得
func (api *API) GetInterfaceAPI() *InterfaceAPI {
	return api.Interface
}

// GetInternetAPI ルーターAPI取得
func (api *API) GetInternetAPI() *InternetAPI {
	return api.Internet
}

// GetIPAddressAPI IPアドレスAPI取得
func (api *API) GetIPAddressAPI() *IPAddressAPI {
	return api.IPAddress
}

// GetIPv6AddrAPI IPv6アドレスAPI取得
func (api *API) GetIPv6AddrAPI() *IPv6AddrAPI {
	return api.IPv6Addr
}

// GetIPv6NetAPI  IPv6ネットワークAPI取得
func (api *API) GetIPv6NetAPI() *IPv6NetAPI {
	return api.IPv6Net
}

// GetLicenseAPI  ライセンスAPI取得
func (api *API) GetLicenseAPI() *LicenseAPI {
	return api.License
}

// GetLoadBalancerAPI ロードバランサーAPI取得
func (api *API) GetLoadBalancerAPI() *LoadBalancerAPI {
	return api.LoadBalancer
}

// GetMobileGatewayAPI モバイルゲートウェイAPI取得
func (api *API) GetMobileGatewayAPI() *MobileGatewayAPI {
	return api.MobileGateway
}

// GetNewsFeedAPI フィード(障害/メンテナンス情報)API取得
func (api *API) GetNewsFeedAPI() *NewsFeedAPI {
	return api.NewsFeed
}

// GetNFSAPI NFS API取得
func (api *API) GetNFSAPI() *NFSAPI {
	return api.NFS
}

// GetNoteAPI スタートアップAPI取得
func (api *API) GetNoteAPI() *NoteAPI {
	return api.Note
}

// GetPacketFilterAPI パケットフィルタAPI取得
func (api *API) GetPacketFilterAPI() *PacketFilterAPI {
	return api.PacketFilter
}

// GetPrivateHostAPI 専有ホストAPI取得
func (api *API) GetPrivateHostAPI() *PrivateHostAPI {
	return api.PrivateHost
}

// GetProductServerAPI サーバープランAPI取得
func (api *API) GetProductServerAPI() *ProductServerAPI {
	return api.Product.GetProductServerAPI()
}

// GetProductLicenseAPI ライセンスプランAPI取得
func (api *API) GetProductLicenseAPI() *ProductLicenseAPI {
	return api.Product.GetProductLicenseAPI()
}

// GetProductDiskAPI ディスクプランAPI取得
func (api *API) GetProductDiskAPI() *ProductDiskAPI {
	return api.Product.GetProductDiskAPI()
}

// GetProductInternetAPI ルータープランAPI取得
func (api *API) GetProductInternetAPI() *ProductInternetAPI {
	return api.Product.GetProductInternetAPI()
}

// GetPublicPriceAPI 価格情報API取得
func (api *API) GetPublicPriceAPI() *PublicPriceAPI {
	return api.Product.GetPublicPriceAPI()
}

// GetServerAPI サーバーAPI取得
func (api *API) GetServerAPI() *ServerAPI {
	return api.Server
}

// GetSIMAPI SIM API取得
func (api *API) GetSIMAPI() *SIMAPI {
	return api.SIM
}

// GetSimpleMonitorAPI シンプル監視API取得
func (api *API) GetSimpleMonitorAPI() *SimpleMonitorAPI {
	return api.SimpleMonitor
}

// GetSSHKeyAPI SSH公開鍵API取得
func (api *API) GetSSHKeyAPI() *SSHKeyAPI {
	return api.SSHKey
}

// GetSubnetAPI サブネットAPI取得
func (api *API) GetSubnetAPI() *SubnetAPI {
	return api.Subnet
}

// GetSwitchAPI スイッチAPI取得
func (api *API) GetSwitchAPI() *SwitchAPI {
	return api.Switch
}

// GetVPCRouterAPI VPCルーターAPI取得
func (api *API) GetVPCRouterAPI() *VPCRouterAPI {
	return api.VPCRouter
}

// GetWebAccelAPI ウェブアクセラレータAPI取得
func (api *API) GetWebAccelAPI() *WebAccelAPI {
	return api.WebAccel
}

// ProductAPI 製品情報関連API群
type ProductAPI struct {
	Server      *ProductServerAPI      // サーバープランAPI
	License     *ProductLicenseAPI     // ライセンスプランAPI
	Disk        *ProductDiskAPI        // ディスクプランAPI
	Internet    *ProductInternetAPI    // ルータープランAPI
	PrivateHost *ProductPrivateHostAPI // 専有ホストプランAPI
	Price       *PublicPriceAPI        // 価格情報API
}

// GetProductServerAPI サーバープランAPI取得
func (api *ProductAPI) GetProductServerAPI() *ProductServerAPI {
	return api.Server
}

// GetProductLicenseAPI ライセンスプランAPI取得
func (api *ProductAPI) GetProductLicenseAPI() *ProductLicenseAPI {
	return api.License
}

// GetProductDiskAPI ディスクプランAPI取得
func (api *ProductAPI) GetProductDiskAPI() *ProductDiskAPI {
	return api.Disk
}

// GetProductInternetAPI ルータープランAPI取得
func (api *ProductAPI) GetProductInternetAPI() *ProductInternetAPI {
	return api.Internet
}

// GetProductPrivateHostAPI 専有ホストプラン取得API
func (api *ProductAPI) GetProductPrivateHostAPI() *ProductPrivateHostAPI {
	return api.PrivateHost
}

// GetPublicPriceAPI 価格情報API取得
func (api *ProductAPI) GetPublicPriceAPI() *PublicPriceAPI {
	return api.Price
}

// FacilityAPI ファシリティ関連API群
type FacilityAPI struct {
	Region *RegionAPI // リージョンAPI
	Zone   *ZoneAPI   // ゾーンAPI
}

// GetRegionAPI リージョンAPI取得
func (api *FacilityAPI) GetRegionAPI() *RegionAPI {
	return api.Region
}

// GetZoneAPI ゾーンAPI取得
func (api *FacilityAPI) GetZoneAPI() *ZoneAPI {
	return api.Zone
}

func newAPI(client *Client) *API {
	return &API{
		AuthStatus: NewAuthStatusAPI(client),
		AutoBackup: NewAutoBackupAPI(client),
		Archive:    NewArchiveAPI(client),
		Bill:       NewBillAPI(client),
		Bridge:     NewBridgeAPI(client),
		CDROM:      NewCDROMAPI(client),
		Database:   NewDatabaseAPI(client),
		Disk:       NewDiskAPI(client),
		DNS:        NewDNSAPI(client),
		Facility: &FacilityAPI{
			Region: NewRegionAPI(client),
			Zone:   NewZoneAPI(client),
		},
		GSLB:          NewGSLBAPI(client),
		Icon:          NewIconAPI(client),
		Interface:     NewInterfaceAPI(client),
		Internet:      NewInternetAPI(client),
		IPAddress:     NewIPAddressAPI(client),
		IPv6Addr:      NewIPv6AddrAPI(client),
		IPv6Net:       NewIPv6NetAPI(client),
		License:       NewLicenseAPI(client),
		LoadBalancer:  NewLoadBalancerAPI(client),
		MobileGateway: NewMobileGatewayAPI(client),
		NewsFeed:      NewNewsFeedAPI(client),
		NFS:           NewNFSAPI(client),
		Note:          NewNoteAPI(client),
		PacketFilter:  NewPacketFilterAPI(client),
		PrivateHost:   NewPrivateHostAPI(client),
		Product: &ProductAPI{
			Server:      NewProductServerAPI(client),
			License:     NewProductLicenseAPI(client),
			Disk:        NewProductDiskAPI(client),
			Internet:    NewProductInternetAPI(client),
			PrivateHost: NewProductPrivateHostAPI(client),
			Price:       NewPublicPriceAPI(client),
		},
		Server:        NewServerAPI(client),
		SIM:           NewSIMAPI(client),
		SimpleMonitor: NewSimpleMonitorAPI(client),
		SSHKey:        NewSSHKeyAPI(client),
		Subnet:        NewSubnetAPI(client),
		Switch:        NewSwitchAPI(client),
		VPCRouter:     NewVPCRouterAPI(client),
		WebAccel:      NewWebAccelAPI(client),
	}
}
