package stfe

import (
	"fmt"
	"strconv"

	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"io/ioutil"
	"net/http"

	"github.com/google/certificate-transparency-go/tls"
	"github.com/google/trillian"
)

// AddEntryRequest is a collection of add-entry input parameters
type AddEntryRequest struct {
	Item        string `json:"item"`        // base64-encoded StItem
	Signature   string `json:"signature"`   // base64-encoded DigitallySigned
	Certificate string `json:"certificate"` // base64-encoded X.509 certificate
}

// GetEntriesRequest is a collection of get-entry input parameters
type GetEntriesRequest struct {
	Start int64 `json:"start"` // 0-based and inclusive start-index
	End   int64 `json:"end"`   // 0-based and inclusive end-index
}

// GetProofByHashRequest is a collection of get-proof-by-hash input parameters
type GetProofByHashRequest struct {
	Hash     []byte `json:"hash"`      // base64-encoded leaf hash
	TreeSize int64  `json:"tree_size"` // Tree head size to base proof on
}

// GetConsistencyProofRequest is a collection of get-consistency-proof input
// parameters
type GetConsistencyProofRequest struct {
	First  int64 `json:"first"`
	Second int64 `json:"second"`
}

// AddEntryResponse is an assembled add-entry response
type AddEntryResponse struct {
	SignedDebugInfo string `json:"sdi"`
}

// GetEntryResponse is an assembled log entry and its associated appendix
type GetEntryResponse struct {
	Leaf      string   `json:"leaf"`      // base64-encoded StItem
	Signature string   `json:"signature"` // base64-encoded signature
	Chain     []string `json:"chain"`     // base64-encoded X.509 certificates
}

// GetEntriesResponse is an assembled get-entries responses
type GetEntriesResponse struct {
	Entries []GetEntryResponse `json:"entries"`
}

// GetProofByHashResponse is an assembled inclusion proof response
type GetProofByHashResponse struct {
	InclusionProof string `json:"inclusion_proof"` // base64-encoded StItem
}

type GetConsistencyProofResponse struct {
	ConsistencyProof string `json:"consistency_proof"` // base64-encoded StItem
}

// GetAnchorsResponse is an assembled get-anchor response
type GetAnchorsResponse struct {
	Certificates []string `json:"certificates"`
}

// GetSthResponse is an assembled get-sth response
type GetSthResponse struct {
	SignedTreeHead string `json:"sth"` // base64-encoded StItem
}

// NewAddEntryRequest parses and sanitizes the JSON-encoded add-entry
// parameters from an incoming HTTP post.  The resulting AddEntryRequest is
// well-formed, but not necessarily trusted (further sanitization is needed).
func NewAddEntryRequest(r *http.Request) (AddEntryRequest, error) {
	var ret AddEntryRequest
	if err := UnpackJsonPost(r, &ret); err != nil {
		return ret, err
	}

	item, err := StItemFromB64(ret.Item)
	if err != nil {
		return ret, fmt.Errorf("failed decoding StItem: %v", err)
	}
	if item.Format != StFormatChecksumV1 {
		return ret, fmt.Errorf("invalid StItem format: %s", item.Format)
	}
	// TODO: verify that we got a checksum length
	// TODO: verify that we got a signature and certificate
	return ret, nil
}

// NewGetEntriesRequest parses and sanitizes the URL-encoded get-entries
// parameters from an incoming HTTP request.
func NewGetEntriesRequest(httpRequest *http.Request) (GetEntriesRequest, error) {
	start, err := strconv.ParseInt(httpRequest.FormValue("start"), 10, 64)
	if err != nil {
		return GetEntriesRequest{}, fmt.Errorf("bad start parameter: %v", err)
	}
	end, err := strconv.ParseInt(httpRequest.FormValue("end"), 10, 64)
	if err != nil {
		return GetEntriesRequest{}, fmt.Errorf("bad end parameter: %v", err)
	}

	if start < 0 {
		return GetEntriesRequest{}, fmt.Errorf("bad parameters: start(%v) must have a non-negative value", start)
	}
	if start > end {
		return GetEntriesRequest{}, fmt.Errorf("bad parameters: start(%v) must be less than or equal to end(%v)", start, end)
	}
	// TODO: check that range is not larger than the max range. Yes -> truncate
	// TODO: check that end is not past the most recent STH. Yes -> truncate
	return GetEntriesRequest{Start: start, End: end}, nil
}

// NewGetProofByHashRequest parses and sanitizes the URL-encoded
// get-proof-by-hash parameters from an incoming HTTP request.
func NewGetProofByHashRequest(httpRequest *http.Request) (GetProofByHashRequest, error) {
	treeSize, err := strconv.ParseInt(httpRequest.FormValue("tree_size"), 10, 64)
	if err != nil {
		return GetProofByHashRequest{}, fmt.Errorf("bad tree_size parameter: %v", err)
	}
	if treeSize < 0 {
		return GetProofByHashRequest{}, fmt.Errorf("bad tree_size parameter: negative value")
	}
	// TODO: check that tree size is not past STH.tree_size

	hash, err := base64.StdEncoding.DecodeString(httpRequest.FormValue("hash"))
	if err != nil {
		return GetProofByHashRequest{}, fmt.Errorf("bad hash parameter: %v", err)
	}
	return GetProofByHashRequest{TreeSize: treeSize, Hash: hash}, nil
}

func NewGetConsistencyProofRequest(httpRequest *http.Request) (GetConsistencyProofRequest, error) {
	first, err := strconv.ParseInt(httpRequest.FormValue("first"), 10, 64)
	if err != nil {
		return GetConsistencyProofRequest{}, fmt.Errorf("bad first parameter: %v", err)
	}
	second, err := strconv.ParseInt(httpRequest.FormValue("second"), 10, 64)
	if err != nil {
		return GetConsistencyProofRequest{}, fmt.Errorf("bad second parameter: %v", err)
	}

	if first < 1 {
		return GetConsistencyProofRequest{}, fmt.Errorf("bad parameters: first(%v) must be a natural number", first)
	}
	if first >= second {
		return GetConsistencyProofRequest{}, fmt.Errorf("bad parameters: second(%v) must be larger than first(%v)", first, second)
	}

	return GetConsistencyProofRequest{First: first, Second: second}, nil
}

// NewAddEntryResponse assembles an add-entry response from an SDI
func NewAddEntryResponse(sdi StItem) (AddEntryResponse, error) {
	b, err := tls.Marshal(sdi)
	if err != nil {
		return AddEntryResponse{}, fmt.Errorf("tls marshal failed: %v", err)
	}
	return AddEntryResponse{
		SignedDebugInfo: base64.StdEncoding.EncodeToString(b),
	}, nil
}

// NewGetEntryResponse assembles a log entry and its appendix
func NewGetEntryResponse(leaf, appendix []byte) (GetEntryResponse, error) {
	var app Appendix
	extra, err := tls.Unmarshal(appendix, &app)
	if err != nil {
		return GetEntryResponse{}, fmt.Errorf("failed tls unmarshaling appendix: %v (%v)", err, extra)
	} else if len(extra) > 0 {
		return GetEntryResponse{}, fmt.Errorf("tls umarshal found extra data for appendix: %v", extra)
	}

	chain := make([]string, 0, len(app.Chain))
	for _, c := range app.Chain {
		chain = append(chain, base64.StdEncoding.EncodeToString(c.Data))
	}

	return GetEntryResponse{
		Leaf:      base64.StdEncoding.EncodeToString(leaf),
		Signature: base64.StdEncoding.EncodeToString(app.Signature),
		Chain:     chain,
	}, nil
}

// NewGetEntriesResponse assembles a get-entries response
func NewGetEntriesResponse(leaves []*trillian.LogLeaf) (GetEntriesResponse, error) {
	entries := make([]GetEntryResponse, 0, len(leaves))
	for _, leaf := range leaves {
		entry, err := NewGetEntryResponse(leaf.GetLeafValue(), leaf.GetExtraData())
		if err != nil {
			return GetEntriesResponse{}, err
		}
		entries = append(entries, entry)
	}
	return GetEntriesResponse{entries}, nil
}

// NewGetProofByHashResponse assembles a get-proof-by-hash response
func NewGetProofByHashResponse(logId []byte, treeSize uint64, inclusionProof *trillian.Proof) (*GetProofByHashResponse, error) {
	item := NewInclusionProofV1(logId, treeSize, inclusionProof)
	b, err := tls.Marshal(item)
	if err != nil {
		return nil, fmt.Errorf("tls marshal failed: %v", err)
	}
	return &GetProofByHashResponse{
		InclusionProof: base64.StdEncoding.EncodeToString(b),
	}, nil
}

func NewGetConsistencyProofResponse(logId []byte, first, second int64, consistencyProof *trillian.Proof) (*GetConsistencyProofResponse, error) {
	item := NewConsistencyProofV1(logId, first, second, consistencyProof)
	b, err := tls.Marshal(item)
	if err != nil {
		return nil, fmt.Errorf("tls marshal failed: %v", err)
	}
	return &GetConsistencyProofResponse{
		ConsistencyProof: base64.StdEncoding.EncodeToString(b),
	}, nil
}

func NewGetAnchorsResponse(anchors []*x509.Certificate) GetAnchorsResponse {
	certificates := make([]string, 0, len(anchors))
	for _, certificate := range anchors {
		certificates = append(certificates, base64.StdEncoding.EncodeToString(certificate.Raw))
	}
	return GetAnchorsResponse{Certificates: certificates}
}

func NewGetSthResponse(sth StItem) (GetSthResponse, error) {
	b, err := tls.Marshal(sth)
	if err != nil {
		return GetSthResponse{}, fmt.Errorf("tls marshal failed: %v", err)
	}
	return GetSthResponse{
		SignedTreeHead: base64.StdEncoding.EncodeToString(b),
	}, nil
}

// VerifyAddEntryRequest determines whether a well-formed AddEntryRequest should
// be inserted into the log.  The corresponding leaf and appendix is returned.
func VerifyAddEntryRequest(ld *LogParameters, r AddEntryRequest) ([]byte, []byte, error) {
	item, err := StItemFromB64(r.Item)
	if err != nil {
		return nil, nil, fmt.Errorf("failed decoding StItem: %v", err)
	}

	leaf, err := tls.Marshal(item)
	if err != nil {
		return nil, nil, fmt.Errorf("failed tls marshaling StItem: %v", err)
	} // leaf is the serialized data that should be added to the tree

	c, err := base64.StdEncoding.DecodeString(r.Certificate)
	if err != nil {
		return nil, nil, fmt.Errorf("failed decoding certificate: %v", err)
	}
	certificate, err := x509.ParseCertificate(c)
	if err != nil {
		return nil, nil, fmt.Errorf("failed decoding certificate: %v", err)
	} // certificate is the end-entity certificate that signed leaf

	chain, err := VerifyChain(ld, certificate)
	if err != nil {
		return nil, nil, fmt.Errorf("chain verification failed: %v", err)
	} // chain is a valid path to some trust anchor

	signature, err := base64.StdEncoding.DecodeString(r.Signature)
	if err != nil {
		return nil, nil, fmt.Errorf("failed decoding signature: %v", err)
	}
	if err := VerifySignature(leaf, signature, certificate); err != nil {
		return nil, nil, fmt.Errorf("signature verification failed: %v", err)
	} // signature is valid for certificate

	// TODO: update doc of what signature "is", i.e., w/e x509 does
	// TODO: doc in markdown/api.md what signature schemes we expect
	appendix, err := tls.Marshal(NewAppendix(chain, signature))
	if err != nil {
		return nil, nil, fmt.Errorf("failed tls marshaling appendix: %v", err)
	}

	return leaf, appendix, nil
}

// UnpackJsonPost unpacks a json-encoded HTTP POST request into `unpack`
func UnpackJsonPost(r *http.Request, unpack interface{}) error {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return fmt.Errorf("failed reading request body: %v", err)
	}
	if err := json.Unmarshal(body, &unpack); err != nil {
		return fmt.Errorf("failed parsing json body: %v", err)
	}
	return nil
}

func WriteJsonResponse(response interface{}, w http.ResponseWriter) error {
	json, err := json.Marshal(&response)
	if err != nil {
		return fmt.Errorf("json-encoding failed: %v", err)
	}

	w.Header().Set("Content-Type", "application/json")
	_, err = w.Write(json)
	if err != nil {
		return fmt.Errorf("failed writing json response: %v", err)
	}
	return nil
}