package api

import (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/getkin/kin-openapi/openapi3"
	"github.com/getkin/kin-openapi/openapi3filter"
	"github.com/getkin/kin-openapi/routers"
	legacyrouter "github.com/getkin/kin-openapi/routers/legacy"

	"github.com/up9inc/mizu/shared"
	"github.com/up9inc/mizu/tap/api"
)

const (
	ContractNotApplicable api.ContractStatus = 0
	ContractPassed        api.ContractStatus = 1
	ContractFailed        api.ContractStatus = 2
)

func loadOAS(ctx context.Context) (doc *openapi3.T, contractContent string, router routers.Router, err error) {
	path := fmt.Sprintf("%s%s", shared.ConfigDirPath, shared.ContractFileName)
	bytes, err := ioutil.ReadFile(path)
	if err != nil {
		return
	}
	contractContent = string(bytes)
	loader := &openapi3.Loader{Context: ctx}
	doc, _ = loader.LoadFromData(bytes)
	err = doc.Validate(ctx)
	if err != nil {
		return
	}
	router, _ = legacyrouter.NewRouter(doc)
	return
}

func validateOAS(ctx context.Context, doc *openapi3.T, router routers.Router, req *http.Request, res *http.Response) (isValid bool, reqErr error, resErr error) {
	isValid = true
	reqErr = nil
	resErr = nil

	// Find route
	route, pathParams, err := router.FindRoute(req)
	if err != nil {
		return
	}

	// Validate request
	requestValidationInput := &openapi3filter.RequestValidationInput{
		Request:    req,
		PathParams: pathParams,
		Route:      route,
	}
	if reqErr = openapi3filter.ValidateRequest(ctx, requestValidationInput); reqErr != nil {
		isValid = false
	}

	responseValidationInput := &openapi3filter.ResponseValidationInput{
		RequestValidationInput: requestValidationInput,
		Status:                 res.StatusCode,
		Header:                 res.Header,
	}

	if res.Body != nil {
		body, _ := ioutil.ReadAll(res.Body)
		res.Body = ioutil.NopCloser(bytes.NewBuffer(body))
		responseValidationInput.SetBodyBytes(body)
	}

	// Validate response.
	if resErr = openapi3filter.ValidateResponse(ctx, responseValidationInput); resErr != nil {
		isValid = false
	}

	return
}

func handleOAS(ctx context.Context, doc *openapi3.T, router routers.Router, req *http.Request, res *http.Response, contractContent string) (contract api.Contract) {
	contract = api.Contract{
		Content: contractContent,
		Status:  ContractNotApplicable,
	}

	isValid, reqErr, resErr := validateOAS(ctx, doc, router, req, res)
	if isValid {
		contract.Status = ContractPassed
	} else {
		contract.Status = ContractFailed
		if reqErr != nil {
			contract.RequestReason = reqErr.Error()
		} else {
			contract.RequestReason = ""
		}
		if resErr != nil {
			contract.ResponseReason = resErr.Error()
		} else {
			contract.ResponseReason = ""
		}
	}

	return
}
