package cloudflare

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

	"github.com/pkg/errors"
)

// Railgun represents a Railgun's properties.
type Railgun struct {
	ID             string    `json:"id"`
	Name           string    `json:"name"`
	Status         string    `json:"status"`
	Enabled        bool      `json:"enabled"`
	ZonesConnected int       `json:"zones_connected"`
	Build          string    `json:"build"`
	Version        string    `json:"version"`
	Revision       string    `json:"revision"`
	ActivationKey  string    `json:"activation_key"`
	ActivatedOn    time.Time `json:"activated_on"`
	CreatedOn      time.Time `json:"created_on"`
	ModifiedOn     time.Time `json:"modified_on"`
	UpgradeInfo    struct {
		LatestVersion string `json:"latest_version"`
		DownloadLink  string `json:"download_link"`
	} `json:"upgrade_info"`
}

// RailgunListOptions represents the parameters used to list railguns.
type RailgunListOptions struct {
	Direction string
}

// railgunResponse represents the response from the Create Railgun and the Railgun Details endpoints.
type railgunResponse struct {
	Response
	Result Railgun `json:"result"`
}

// railgunsResponse represents the response from the List Railguns endpoint.
type railgunsResponse struct {
	Response
	Result []Railgun `json:"result"`
}

// CreateRailgun creates a new Railgun.
//
// API reference: https://api.cloudflare.com/#railgun-create-railgun
func (api *API) CreateRailgun(name string) (Railgun, error) {
	uri := api.userBaseURL("") + "/railguns"
	params := struct {
		Name string `json:"name"`
	}{
		Name: name,
	}
	res, err := api.makeRequest("POST", uri, params)
	if err != nil {
		return Railgun{}, errors.Wrap(err, errMakeRequestError)
	}
	var r railgunResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return Railgun{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// ListRailguns lists Railguns connected to an account.
//
// API reference: https://api.cloudflare.com/#railgun-list-railguns
func (api *API) ListRailguns(options RailgunListOptions) ([]Railgun, error) {
	v := url.Values{}
	if options.Direction != "" {
		v.Set("direction", options.Direction)
	}
	uri := api.userBaseURL("") + "/railguns" + "?" + v.Encode()
	res, err := api.makeRequest("GET", uri, nil)
	if err != nil {
		return nil, errors.Wrap(err, errMakeRequestError)
	}
	var r railgunsResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return nil, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// RailgunDetails returns the details for a Railgun.
//
// API reference: https://api.cloudflare.com/#railgun-railgun-details
func (api *API) RailgunDetails(railgunID string) (Railgun, error) {
	uri := api.userBaseURL("") + "/railguns/" + railgunID
	res, err := api.makeRequest("GET", uri, nil)
	if err != nil {
		return Railgun{}, errors.Wrap(err, errMakeRequestError)
	}
	var r railgunResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return Railgun{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// RailgunZones returns the zones that are currently using a Railgun.
//
// API reference: https://api.cloudflare.com/#railgun-get-zones-connected-to-a-railgun
func (api *API) RailgunZones(railgunID string) ([]Zone, error) {
	uri := api.userBaseURL("") + "/railguns/" + railgunID + "/zones"
	res, err := api.makeRequest("GET", uri, nil)
	if err != nil {
		return nil, errors.Wrap(err, errMakeRequestError)
	}
	var r ZonesResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return nil, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// enableRailgun enables (true) or disables (false) a Railgun for all zones connected to it.
//
// API reference: https://api.cloudflare.com/#railgun-enable-or-disable-a-railgun
func (api *API) enableRailgun(railgunID string, enable bool) (Railgun, error) {
	uri := api.userBaseURL("") + "/railguns/" + railgunID
	params := struct {
		Enabled bool `json:"enabled"`
	}{
		Enabled: enable,
	}
	res, err := api.makeRequest("PATCH", uri, params)
	if err != nil {
		return Railgun{}, errors.Wrap(err, errMakeRequestError)
	}
	var r railgunResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return Railgun{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// EnableRailgun enables a Railgun for all zones connected to it.
//
// API reference: https://api.cloudflare.com/#railgun-enable-or-disable-a-railgun
func (api *API) EnableRailgun(railgunID string) (Railgun, error) {
	return api.enableRailgun(railgunID, true)
}

// DisableRailgun enables a Railgun for all zones connected to it.
//
// API reference: https://api.cloudflare.com/#railgun-enable-or-disable-a-railgun
func (api *API) DisableRailgun(railgunID string) (Railgun, error) {
	return api.enableRailgun(railgunID, false)
}

// DeleteRailgun disables and deletes a Railgun.
//
// API reference: https://api.cloudflare.com/#railgun-delete-railgun
func (api *API) DeleteRailgun(railgunID string) error {
	uri := api.userBaseURL("") + "/railguns/" + railgunID
	if _, err := api.makeRequest("DELETE", uri, nil); err != nil {
		return errors.Wrap(err, errMakeRequestError)
	}
	return nil
}

// ZoneRailgun represents the status of a Railgun on a zone.
type ZoneRailgun struct {
	ID        string `json:"id"`
	Name      string `json:"name"`
	Enabled   bool   `json:"enabled"`
	Connected bool   `json:"connected"`
}

// zoneRailgunResponse represents the response from the Zone Railgun Details endpoint.
type zoneRailgunResponse struct {
	Response
	Result ZoneRailgun `json:"result"`
}

// zoneRailgunsResponse represents the response from the Zone Railgun endpoint.
type zoneRailgunsResponse struct {
	Response
	Result []ZoneRailgun `json:"result"`
}

// RailgunDiagnosis represents the test results from testing railgun connections
// to a zone.
type RailgunDiagnosis struct {
	Method          string `json:"method"`
	HostName        string `json:"host_name"`
	HTTPStatus      int    `json:"http_status"`
	Railgun         string `json:"railgun"`
	URL             string `json:"url"`
	ResponseStatus  string `json:"response_status"`
	Protocol        string `json:"protocol"`
	ElapsedTime     string `json:"elapsed_time"`
	BodySize        string `json:"body_size"`
	BodyHash        string `json:"body_hash"`
	MissingHeaders  string `json:"missing_headers"`
	ConnectionClose bool   `json:"connection_close"`
	Cloudflare      string `json:"cloudflare"`
	CFRay           string `json:"cf-ray"`
	// NOTE: Cloudflare's online API documentation does not yet have definitions
	// for the following fields. See: https://api.cloudflare.com/#railgun-connections-for-a-zone-test-railgun-connection/
	CFWANError    string `json:"cf-wan-error"`
	CFCacheStatus string `json:"cf-cache-status"`
}

// railgunDiagnosisResponse represents the response from the Test Railgun Connection enpoint.
type railgunDiagnosisResponse struct {
	Response
	Result RailgunDiagnosis `json:"result"`
}

// ZoneRailguns returns the available Railguns for a zone.
//
// API reference: https://api.cloudflare.com/#railguns-for-a-zone-get-available-railguns
func (api *API) ZoneRailguns(zoneID string) ([]ZoneRailgun, error) {
	uri := "/zones/" + zoneID + "/railguns"
	res, err := api.makeRequest("GET", uri, nil)
	if err != nil {
		return nil, errors.Wrap(err, errMakeRequestError)
	}
	var r zoneRailgunsResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return nil, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// ZoneRailgunDetails returns the configuration for a given Railgun.
//
// API reference: https://api.cloudflare.com/#railguns-for-a-zone-get-railgun-details
func (api *API) ZoneRailgunDetails(zoneID, railgunID string) (ZoneRailgun, error) {
	uri := "/zones/" + zoneID + "/railguns/" + railgunID
	res, err := api.makeRequest("GET", uri, nil)
	if err != nil {
		return ZoneRailgun{}, errors.Wrap(err, errMakeRequestError)
	}
	var r zoneRailgunResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return ZoneRailgun{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// TestRailgunConnection tests a Railgun connection for a given zone.
//
// API reference: https://api.cloudflare.com/#railgun-connections-for-a-zone-test-railgun-connection
func (api *API) TestRailgunConnection(zoneID, railgunID string) (RailgunDiagnosis, error) {
	uri := "/zones/" + zoneID + "/railguns/" + railgunID + "/diagnose"
	res, err := api.makeRequest("GET", uri, nil)
	if err != nil {
		return RailgunDiagnosis{}, errors.Wrap(err, errMakeRequestError)
	}
	var r railgunDiagnosisResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return RailgunDiagnosis{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// connectZoneRailgun connects (true) or disconnects (false) a Railgun for a given zone.
//
// API reference: https://api.cloudflare.com/#railguns-for-a-zone-connect-or-disconnect-a-railgun
func (api *API) connectZoneRailgun(zoneID, railgunID string, connect bool) (ZoneRailgun, error) {
	uri := "/zones/" + zoneID + "/railguns/" + railgunID
	params := struct {
		Connected bool `json:"connected"`
	}{
		Connected: connect,
	}
	res, err := api.makeRequest("PATCH", uri, params)
	if err != nil {
		return ZoneRailgun{}, errors.Wrap(err, errMakeRequestError)
	}
	var r zoneRailgunResponse
	if err := json.Unmarshal(res, &r); err != nil {
		return ZoneRailgun{}, errors.Wrap(err, errUnmarshalError)
	}
	return r.Result, nil
}

// ConnectZoneRailgun connects a Railgun for a given zone.
//
// API reference: https://api.cloudflare.com/#railguns-for-a-zone-connect-or-disconnect-a-railgun
func (api *API) ConnectZoneRailgun(zoneID, railgunID string) (ZoneRailgun, error) {
	return api.connectZoneRailgun(zoneID, railgunID, true)
}

// DisconnectZoneRailgun disconnects a Railgun for a given zone.
//
// API reference: https://api.cloudflare.com/#railguns-for-a-zone-connect-or-disconnect-a-railgun
func (api *API) DisconnectZoneRailgun(zoneID, railgunID string) (ZoneRailgun, error) {
	return api.connectZoneRailgun(zoneID, railgunID, false)
}