package osin

import (
	"errors"
	"fmt"
	"net/http"
	"net/url"
)

// ResponseData for response output
type ResponseData map[string]interface{}

// ResponseType Response type enum
type ResponseType int

// iotas
const (
	DATA ResponseType = iota
	REDIRECT
)

const (
	// EpochExpiresHeader is RFC1123 formatted timestamp for epoch
	EpochExpiresHeader = "Thu, 01 Jan 1970 00:00:00 UTC"
)

// Response is the server response
type Response struct {
	Type               ResponseType
	StatusCode         int
	StatusText         string
	ErrorStatusCode    int
	URL                string
	Output             ResponseData
	Headers            http.Header
	IsError            bool
	ErrorId            string
	InternalError      error
	RedirectInFragment bool

	// Storage to use in this response - required
	Storage Storage
}

// NewResponse generates a new Response
func NewResponse(storage Storage) *Response {
	r := &Response{
		Type:            DATA,
		StatusCode:      200,
		ErrorStatusCode: 200,
		Output:          make(ResponseData),
		Headers:         make(http.Header),
		IsError:         false,
		Storage:         storage.Clone(),
	}
	r.Headers.Add(
		"Cache-Control",
		"no-cache, no-store, max-age=0, must-revalidate",
	)
	r.Headers.Add("Pragma", "no-cache")
	r.Headers.Add("Expires", EpochExpiresHeader)
	return r
}

// AddHeaders adds default headers for oauth2 server responses
func AddHeaders(w http.ResponseWriter) {
	w.Header().Add(
		"Cache-Control",
		"no-cache, no-store, max-age=0, must-revalidate",
	)
	w.Header().Add("Pragma", "no-cache")
	w.Header().Add("Expires", EpochExpiresHeader)
	return
}

// SetError sets an error id and description on the Response
// state and uri are left blank
func (r *Response) SetError(id string, description string) {
	r.SetErrorURI(id, description, "", "")
}

// SetErrorState sets an error id, description, and state on the Response
// uri is left blank
func (r *Response) SetErrorState(id string, description string, state string) {
	r.SetErrorURI(id, description, "", state)
}

// SetErrorURI sets an error id, description, state, and uri on the Response
func (r *Response) SetErrorURI(id string, description string, uri string, state string) {
	// get default error message
	if description == "" {
		description = deferror.Get(id)
	}

	// set error parameters
	r.IsError = true
	r.ErrorId = id
	r.StatusCode = r.ErrorStatusCode
	if r.StatusCode != 200 {
		r.StatusText = description
	} else {
		r.StatusText = ""
	}
	r.Output = make(ResponseData) // clear output
	r.Output["error"] = id
	r.Output["error_description"] = description
	if uri != "" {
		r.Output["error_uri"] = uri
	}
	if state != "" {
		r.Output["state"] = state
	}
}

// SetRedirect changes the response to redirect to the given url
func (r *Response) SetRedirect(url string) {
	// set redirect parameters
	r.Type = REDIRECT
	r.URL = url
}

// SetRedirectFragment sets redirect values to be passed in fragment instead of as query parameters
func (r *Response) SetRedirectFragment(f bool) {
	r.RedirectInFragment = f
}

// BlacklistedQueryValues are keys that should be stripped from query params
var BlacklistedQueryValues = map[string]struct{}{
	"username": struct{}{},
	"password": struct{}{},
}

// GetRedirectUrl returns the redirect url with all query string parameters
func (r *Response) GetRedirectUrl() (string, error) {
	if r.Type != REDIRECT {
		return "", errors.New("Not a redirect response")
	}

	u, err := url.Parse(r.URL)
	if err != nil {
		return "", err
	}

	// add parameters
	q := u.Query()
	for n, v := range r.Output {
		if _, ok := BlacklistedQueryValues[n]; !ok {
			q.Set(n, fmt.Sprint(v))
		}
	}
	if r.RedirectInFragment {
		u.RawQuery = ""
		u.Fragment, err = url.QueryUnescape(q.Encode())
		if err != nil {
			return "", err
		}
	} else {
		u.RawQuery = q.Encode()
	}

	return u.String(), nil
}

// Close closes the response
func (r *Response) Close() {
	r.Storage.Close()
}
