package playwright

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
)

type serializedFallbackOverrides struct {
	URL            *string
	Method         *string
	Headers        map[string]string
	PostDataBuffer []byte
}

type requestImpl struct {
	channelOwner
	timing             *RequestTiming
	provisionalHeaders *rawHeaders
	allHeaders         *rawHeaders
	redirectedFrom     Request
	redirectedTo       Request
	failureText        string
	fallbackOverrides  *serializedFallbackOverrides
}

func (r *requestImpl) URL() string {
	if r.fallbackOverrides.URL != nil {
		return *r.fallbackOverrides.URL
	}
	return r.initializer["url"].(string)
}

func (r *requestImpl) ResourceType() string {
	return r.initializer["resourceType"].(string)
}

func (r *requestImpl) Method() string {
	if r.fallbackOverrides.Method != nil {
		return *r.fallbackOverrides.Method
	}
	return r.initializer["method"].(string)
}

func (r *requestImpl) PostData() (string, error) {
	body, err := r.PostDataBuffer()
	if err != nil {
		return "", err
	}
	return string(body), err
}

func (r *requestImpl) PostDataJSON(v interface{}) error {
	body, err := r.PostDataBuffer()
	if err != nil {
		return err
	}
	return json.Unmarshal(body, v)
}

func (r *requestImpl) PostDataBuffer() ([]byte, error) {
	if r.fallbackOverrides.PostDataBuffer != nil {
		return r.fallbackOverrides.PostDataBuffer, nil
	}
	if _, ok := r.initializer["postData"]; !ok {
		return nil, nil
	}
	return base64.StdEncoding.DecodeString(r.initializer["postData"].(string))
}

func (r *requestImpl) Headers() map[string]string {
	if r.fallbackOverrides.Headers != nil {
		return newRawHeaders(serializeMapToNameAndValue(r.fallbackOverrides.Headers)).Headers()
	}
	return r.provisionalHeaders.Headers()
}

func (r *requestImpl) Response() (Response, error) {
	channel, err := r.channel.Send("response")
	if err != nil {
		return nil, err
	}
	channelOwner := fromNullableChannel(channel)
	if channelOwner == nil {
		// no response
		return nil, nil
	}
	return channelOwner.(*responseImpl), nil
}

func (r *requestImpl) Frame() Frame {
	channel, ok := r.initializer["frame"]
	if !ok {
		// Service Worker requests do not have an associated frame.
		return nil
	}
	frame := fromChannel(channel).(*frameImpl)
	if frame.page == nil {
		// Frame for this navigation request is not available, because the request
		// was issued before the frame is created. You can check whether the request
		// is a navigation request by calling IsNavigationRequest() method.
		return nil
	}
	return frame
}

func (r *requestImpl) IsNavigationRequest() bool {
	return r.initializer["isNavigationRequest"].(bool)
}

func (r *requestImpl) RedirectedFrom() Request {
	return r.redirectedFrom
}

func (r *requestImpl) RedirectedTo() Request {
	return r.redirectedTo
}

func (r *requestImpl) Failure() error {
	if r.failureText == "" {
		return nil
	}
	return fmt.Errorf("%v", r.failureText)
}

func (r *requestImpl) Timing() *RequestTiming {
	return r.timing
}

func (r *requestImpl) AllHeaders() (map[string]string, error) {
	headers, err := r.ActualHeaders()
	if err != nil {
		return nil, err
	}
	return headers.Headers(), nil
}

func (r *requestImpl) HeadersArray() ([]NameValue, error) {
	headers, err := r.ActualHeaders()
	if err != nil {
		return nil, err
	}
	return headers.HeadersArray(), nil
}

func (r *requestImpl) HeaderValue(name string) (string, error) {
	headers, err := r.ActualHeaders()
	if err != nil {
		return "", err
	}
	return headers.Get(name), err
}

func (r *requestImpl) HeaderValues(name string) ([]string, error) {
	headers, err := r.ActualHeaders()
	if err != nil {
		return []string{}, err
	}
	return headers.GetAll(name), err
}

func (r *requestImpl) ActualHeaders() (*rawHeaders, error) {
	if r.fallbackOverrides.Headers != nil {
		return newRawHeaders(serializeMapToNameAndValue(r.fallbackOverrides.Headers)), nil
	}
	if r.allHeaders == nil {
		response, err := r.Response()
		if err != nil {
			return nil, err
		}
		if response == nil {
			return r.provisionalHeaders, nil
		}
		headers, err := r.channel.Send("rawRequestHeaders")
		if err != nil {
			return nil, err
		}
		r.allHeaders = newRawHeaders(headers)
	}
	return r.allHeaders, nil
}

func (r *requestImpl) ServiceWorker() Worker {
	channel, ok := r.initializer["serviceWorker"]
	if !ok {
		return nil
	}
	return fromChannel(channel).(*workerImpl)
}

func (r *requestImpl) Sizes() (*RequestSizesResult, error) {
	response, err := r.Response()
	if err != nil {
		return nil, err
	}
	sizes, err := response.(*responseImpl).channel.Send("sizes")
	if err != nil {
		return nil, err
	}
	result := &RequestSizesResult{}
	remapMapToStruct(sizes, result)
	return result, nil
}

func (r *requestImpl) applyFallbackOverrides(options RouteFallbackOptions) {
	if options.URL != nil {
		r.fallbackOverrides.URL = options.URL
	}
	if options.Method != nil {
		r.fallbackOverrides.Method = options.Method
	}
	r.fallbackOverrides.Headers = options.Headers
	if options.PostData != nil {
		switch v := options.PostData.(type) {
		case string:
			r.fallbackOverrides.PostDataBuffer = []byte(v)
		case []byte:
			r.fallbackOverrides.PostDataBuffer = v
		}
	}
}

func (r *requestImpl) targetClosed() <-chan error {
	page := r.safePage()
	if page == nil {
		return make(<-chan error, 1)
	}
	return page.closedOrCrashed
}

func (r *requestImpl) setResponseEndTiming(t float64) {
	r.timing.ResponseEnd = t
	if r.timing.ResponseStart == -1 {
		r.timing.ResponseStart = t
	}
}

func (r *requestImpl) safePage() *pageImpl {
	channel := fromNullableChannel(r.initializer["frame"])
	if channel == nil {
		return nil
	}
	frame, ok := channel.(*frameImpl)
	if !ok {
		return nil
	}
	return frame.page
}

func newRequest(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *requestImpl {
	req := &requestImpl{}
	req.createChannelOwner(req, parent, objectType, guid, initializer)
	redirectedFrom := fromNullableChannel(initializer["redirectedFrom"])
	if redirectedFrom != nil {
		req.redirectedFrom = redirectedFrom.(*requestImpl)
	}
	if req.redirectedFrom != nil {
		req.redirectedFrom.(*requestImpl).redirectedTo = req
	}
	req.timing = &RequestTiming{
		StartTime:             0,
		DomainLookupStart:     -1,
		DomainLookupEnd:       -1,
		ConnectStart:          -1,
		SecureConnectionStart: -1,
		ConnectEnd:            -1,
		RequestStart:          -1,
		ResponseStart:         -1,
		ResponseEnd:           -1,
	}
	req.provisionalHeaders = newRawHeaders(req.initializer["headers"])
	req.fallbackOverrides = &serializedFallbackOverrides{}
	return req
}
