package drivers

import (
	"context"

	"github.com/wI2L/jettison"

	"github.com/MontFerret/ferret/pkg/runtime/core"
	"github.com/MontFerret/ferret/pkg/runtime/values"
	"github.com/MontFerret/ferret/pkg/runtime/values/types"
)

// HTTPResponse HTTP response object.
type HTTPResponse struct {
	URL          string
	StatusCode   int
	Status       string
	Headers      *HTTPHeaders
	ResponseTime float64
}

func (resp *HTTPResponse) Type() core.Type {
	return HTTPResponseType
}

func (resp *HTTPResponse) String() string {
	return resp.Status
}

func (resp *HTTPResponse) Compare(other core.Value) int64 {
	if other.Type() != HTTPResponseType {
		return Compare(HTTPResponseType, other.Type())
	}

	// this is a safe cast. Only *HTTPResponse implements core.Value.
	// HTTPResponse does not.
	otherResp := other.(*HTTPResponse)

	comp := resp.Headers.Compare(otherResp.Headers)
	if comp != 0 {
		return comp
	}

	// it makes no sense to compare Status strings
	// because they are always equal if StatusCode's are equal
	return values.NewInt(resp.StatusCode).
		Compare(values.NewInt(resp.StatusCode))
}

func (resp *HTTPResponse) Unwrap() interface{} {
	return resp
}

func (resp *HTTPResponse) Copy() core.Value {
	return *(&resp)
}

func (resp *HTTPResponse) Hash() uint64 {
	return values.Parse(resp).Hash()
}

// responseMarshal is a structure that repeats HTTPResponse. It allows
// easily Marshal the HTTPResponse object.
type responseMarshal struct {
	URL          string       `json:"url"`
	StatusCode   int          `json:"status_code"`
	Status       string       `json:"status"`
	Headers      *HTTPHeaders `json:"headers"`
	ResponseTime float64      `json:"response_time"`
}

func (resp *HTTPResponse) MarshalJSON() ([]byte, error) {
	if resp == nil {
		return values.None.MarshalJSON()
	}

	return jettison.MarshalOpts(responseMarshal(*resp), jettison.NoHTMLEscaping())
}

func (resp *HTTPResponse) GetIn(ctx context.Context, path []core.Value) (core.Value, core.PathError) {
	if len(path) == 0 {
		return resp, nil
	}

	segmentIdx := 0
	segment := path[segmentIdx]

	if typ := segment.Type(); typ != types.String {
		return values.None, core.NewPathError(core.TypeError(typ, types.String), segmentIdx)
	}

	field := segment.String()

	switch field {
	case "url", "URL":
		return values.NewString(resp.URL), nil
	case "status":
		return values.NewString(resp.Status), nil
	case "statusCode":
		return values.NewInt(resp.StatusCode), nil
	case "headers":
		if len(path) == 1 {
			return resp.Headers, nil
		}

		out, pathErr := resp.Headers.GetIn(ctx, path[1:])

		if pathErr != nil {
			return values.None, core.NewPathErrorFrom(pathErr, segmentIdx)
		}

		return out, nil
	case "responseTime":
		return values.NewFloat(resp.ResponseTime), nil

	}

	return values.None, nil
}