package api

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strings"

	"gordenko.dev/dima/fixme"
	"gordenko.dev/dima/web"
)

// EXTRA FUNCS

func StateToIP(state *web.State) string {
	return state.Request().RemoteAddr
}

func StateToAuthToken(state *web.State) (string, error) {
	header := state.Request().Header.Get("Authorization")
	if header == "" {
		return "", fix.Error(MissingAuthorizationHeader)
	}
	token, found := strings.CutPrefix(header, "Bearer ")
	if !found {
		return "", fix.Error(MalformedAuthorizationHeader)
	}
	return token, nil
}

// EXTRACT REQUEST FUNCS

type RPCRequest struct {
	funcName string
	data     json.RawMessage
	sid      string // SID
}

func (s RPCRequest) FuncName() string {
	return s.funcName
}

func (s RPCRequest) Data() json.RawMessage {
	return s.data
}

func (s RPCRequest) AuthToken() (string, error) {
	return s.sid, nil
}

type RPCEnvelope struct {
	FuncName string          `json:"func"`
	SID      string          `json:"sid,omitempty"`
	Data     json.RawMessage `json:"data"`
}

func ExtractRPCRequest(state *web.State) (_ APIRequest, err error) {
	body, err := state.ReadBody()
	if err != nil {
		return
	}
	var env RPCEnvelope
	if err = json.Unmarshal(body, &env); err != nil {
		err = fixme.Error{
			Code:    BadRequestBody,
			Message: err.Error(),
		}
		return
	}
	return RPCRequest{
		funcName: env.FuncName,
		data:     env.Data,
		sid:      env.SID,
	}, nil
}

type RESTRequest struct {
	funcName            string
	data                json.RawMessage
	authorizationHeader string
}

func (s RESTRequest) FuncName() string {
	return s.funcName
}

func (s RESTRequest) Data() json.RawMessage {
	return s.data
}

func (s RESTRequest) AuthToken() (string, error) {
	if s.authorizationHeader == "" {
		return "", fix.Error(MissingAuthorizationHeader)
	}
	accessToken, found := strings.CutPrefix(s.authorizationHeader, "Bearer ")
	if !found {
		return "", fix.Error(MalformedAuthorizationHeader)
	}
	return accessToken, nil
}

type RESTRequestExtractor struct {
	pathPrefix string
}

func NewRESTRequestExtractor(pathPrefix string) (_ RESTRequestExtractor, err error) {
	if pathPrefix == "" {
		err = errors.New("pathPrefix argument is required")
		return
	}
	if !strings.HasPrefix(pathPrefix, "/") {
		err = errors.New("pathPrefix must starts with /")
		return
	}
	return RESTRequestExtractor{
		pathPrefix: pathPrefix,
	}, nil
}

func (s RESTRequestExtractor) Extract(state *web.State) (_ APIRequest, err error) {
	httpReq := state.Request()
	body, err := state.ReadBody()
	if err != nil {
		return
	}
	return RESTRequest{
		funcName:            strings.TrimPrefix(httpReq.URL.Path, s.pathPrefix),
		data:                body,
		authorizationHeader: httpReq.Header.Get("Authorization"),
	}, nil
}

// WRITE RESPONSE FUNCS

func WriteRPCResponse(reply *web.Reply, result any, err error) {
	if err != nil {
		switch err.(type) {
		case fixme.Field, fixme.Item, fixme.Error:
			// pass
		default:
			err = fix.Error(InternalError)
		}
		reply.AsJSON(fixme.ErrorEnvelope{
			Error: err,
		})
	} else {
		if result == nil {
			reply.WriteString("{}")
		} else {
			reply.AsJSON(result)
		}
	}
}

func WriteRESTResponse(reply *web.Reply, result any, err error) {
	if err != nil {
		reply.Code = 500
		var fixable fixme.Error
		if errors.As(err, &fixable) {
			switch fixable.Code {
			case TokenExpired, InvalidToken, MissingAuthorizationHeader, MalformedAuthorizationHeader:
				reply.Code = 401
			}
		}
		switch err.(type) {
		case fixme.Field, fixme.Item, fixme.Error:
			// pass
		default:
			err = fix.Error(InternalError)
		}
		reply.AsJSON(fixme.ErrorEnvelope{
			Error: err,
		})
	} else {
		if result == nil {
			reply.WriteString("{\"success\":true}")
		} else {
			reply.AsJSON(result)
		}
	}
}

// BUILD HTTP REQUESTS (client only)

func BuildEnvelopeRequest(req BuildRequestReq) (*http.Request, error) {
	encodedData, err := json.Marshal(req.Data)
	if err != nil {
		return nil, fmt.Errorf("json.Marshal(data): %s", err)
	}
	encodedReq, err := json.Marshal(RPCEnvelope{
		FuncName: req.FuncName,
		Data:     encodedData,
		SID:      req.AuthToken,
	})
	if err != nil {
		return nil, fmt.Errorf("json.Marshal(APIRequest): %s", err)
	}
	x, err := http.NewRequest(http.MethodPost, req.URL, bytes.NewReader(encodedReq))
	if err != nil {
		return nil, fmt.Errorf("http.NewRequest: %s", err)
	}
	return x, nil
}

func BuildRESTRequest(req BuildRequestReq) (*http.Request, error) {
	if !strings.HasPrefix(req.FuncName, "/") {
		return nil, fmt.Errorf("FuncName must starts with /")
	}
	encodedData, err := json.Marshal(req.Data)
	if err != nil {
		return nil, fmt.Errorf("json.Marshal(data): %s", err)
	}
	x, err := http.NewRequest(http.MethodPost, req.URL+req.FuncName, bytes.NewReader(encodedData))
	if err != nil {
		return nil, fmt.Errorf("http.NewRequest: %s", err)
	}
	if req.AuthToken != "" {
		x.Header.Set("Authorization", "Bearer "+req.AuthToken)
	}
	return x, nil
}
