package fakes

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
)

const (
	CREATE = "CREATE"
	UPDATE = "UPDATE"
	GET    = "GET"
)

type fakeCallistoAPI struct{}

func NewFakeCallistoAPIClient() *fakeCallistoAPI {
	return &fakeCallistoAPI{}
}

type fakeResponse struct {
	response   string
	statusCode int
}

func (c *fakeCallistoAPI) DoProxy(r *http.Request, path string, extraHeaders map[string]string) (*http.Response, error) {
	var err error
	var fakeResponse *fakeResponse = nil

	switch true {
	case r.Method == http.MethodPut:
		fakeResponse, err = createFromAuctionCondition(path)
	case r.Method == http.MethodPost:
		fakeResponse, err = postConditions(r, path)
	case r.Method == http.MethodGet:
		fakeResponse, err = proxyCondition(path)
	default:
		return nil, fmt.Errorf("method not implement yet in mock callisto api")
	}
	if err != nil {
		return nil, err
	}

	return generateSOResponse(fakeResponse), nil
}

func createFromAuctionCondition(path string) (*fakeResponse, error) {
	url, err := url.Parse(path)
	if err != nil {
		return nil, err
	}
	prefix := strings.TrimPrefix(url.Path, "/v1/bff/bids/")
	condition := strings.TrimSuffix(prefix, "/winner")
	response := &fakeResponse{
		response:   getResponseForCreate(condition, SOResponseFromAuction),
		statusCode: getStatusCode(condition),
	}
	return response, nil
}

func proxyCondition(path string) (*fakeResponse, error) {
	url, err := url.Parse(path)
	if err != nil {
		return nil, err
	}
	condition := strings.TrimPrefix(url.Path, "/proxy/")
	conditionNew := condition
	if conditionNew == Success {
		conditionNew = SuccessProxy
	} else if strings.Contains(path, "/v1/") {
		splitted := strings.Split(url.Path, "/proxy/")
		conditionNew = splitted[0]
		condition = splitted[1]
	}

	switch conditionNew {
	case V1AuthMe:
		resp, code := generateAuthResponse(condition)
		return &fakeResponse{
			response:   resp,
			statusCode: code,
		}, nil
	case SaleOrderSummary:
		resp, code := generateDefaultResponse(condition)
		return &fakeResponse{
			response:   resp,
			statusCode: code,
		}, nil
	}

	response := &fakeResponse{
		response:   getResponseForCreate(conditionNew, condition),
		statusCode: getStatusCode(condition),
	}
	return response, nil
}

func postConditions(r *http.Request, path string) (*fakeResponse, error) {
	// ATM we should hamdle 3 different paths for POST methodspath
	// - create order
	// - update legacy order
	// - update legacy with fake Put method
	url, err := url.Parse(path)
	if err != nil {
		return nil, err
	}
	orderID := strings.TrimPrefix(url.Path, "/v1/bff/sale-orders")
	if orderID == "" {
		return createFromCartCondition(r)
	}
	return updateCondition(r, path)
}

func createFromCartCondition(r *http.Request) (*fakeResponse, error) {
	reqBody, err := getBody(r)
	if err != nil {
		return nil, err
	}
	condition, ok := reqBody["customer_po"]
	if !ok {
		return nil, errors.New("not able to return mock customer po")
	}
	response := &fakeResponse{
		response:   getResponseForCreate(condition.(string), SOResponseFromCart),
		statusCode: getStatusCode(condition.(string)),
	}
	return response, nil
}

func updateCondition(r *http.Request, path string) (*fakeResponse, error) {
	url, err := url.Parse(path)
	if err != nil {
		return nil, err
	}
	condition := strings.TrimPrefix(url.Path, "/v1/bff/sale-orders/")
	response := &fakeResponse{
		response:   getResponseForUpdate(condition, SOResponseUpdatePUT),
		statusCode: getStatusCode(condition),
	}
	return response, nil
}

func (c *fakeCallistoAPI) Send(r *http.Request) (*http.Response, error) {
	resp := &http.Response{}
	return resp, nil
}

func generateSOResponse(fr *fakeResponse) *http.Response {
	resp := &http.Response{}
	resp.StatusCode = fr.statusCode
	resp.Body = io.NopCloser(bytes.NewBuffer([]byte(fr.response)))
	return resp
}

func getStatusCode(condition string) int {
	var statusCode int
	switch {
	case condition == ErrorUnauthorized:
		statusCode = http.StatusUnauthorized
	case condition == NotFound:
		statusCode = http.StatusNotFound
	default:
		statusCode = http.StatusOK
	}
	return statusCode
}

func getResponseForCreate(condition, response string) string {
	var mockResponse string
	switch {
	case condition == ErrorUnauthorized:
		mockResponse = httpBodyResponseUnauthorized
	case condition == NotFound:
		mockResponse = http.StatusText(http.StatusNotFound)
	case condition == NoBFFResponse:
		mockResponse = fmt.Sprintf(response, "")
	case condition == InvalidBFF:
		mockResponse = fmt.Sprintf(response, invalidBff)
	case condition == BFFNoOrders:
		mockResponse = fmt.Sprintf(response, bffNoOrders)
	case condition == SuccessProxy:
		mockResponse = response
	default:
		mockResponse = fmt.Sprintf(response, validBFF)
	}
	return mockResponse
}

func getResponseForUpdate(condition, response string) string {
	var mockResponse string
	switch {
	case condition == ErrorUnauthorized:
		mockResponse = httpBodyResponseUnauthorized
	case condition == NotFound:
		mockResponse = http.StatusText(http.StatusNotFound)
	case condition == NoBFFResponse:
		mockResponse = fmt.Sprintf(response, "", SOResponseUpdatePUTData)
	case condition == InvalidBFF:
		mockResponse = fmt.Sprintf(response, invalidUpdatePutBff, SOResponseUpdatePUTData)
	default:
		mockResponse = fmt.Sprintf(response, validUpdatePutBFF, SOResponseUpdatePUTData)
	}
	return mockResponse
}

func getBody(r *http.Request) (map[string]any, error) {
	var interceptData map[string]any
	b, err := io.ReadAll(r.Body)
	if err != nil {
		return nil, err
	}

	// after success read put back the body content in the request
	r.Body = io.NopCloser(bytes.NewBuffer(b))
	if err := json.Unmarshal(b, &interceptData); err != nil {
		return nil, err
	}
	return interceptData, nil
}

func generateAuthResponse(responseCase string) (string, int) {
	switch responseCase {
	case "success":
		return `{"company_id": 1}`, http.StatusOK
	case "success-0":
		return `{"company_id": 0}`, http.StatusOK
	case "failed":
		return `{"error": "error from api"}`, http.StatusInternalServerError
	default:
		return httpBodyResponseUnauthorized, http.StatusUnauthorized
	}
}

func generateDefaultResponse(responseCase string) (string, int) {
	switch responseCase {
	case "success":
		return `{"success": true}`, http.StatusOK
	case "failed":
		return `{"success": false, "error": {"code": 500,"message": "error from api"}}`, http.StatusInternalServerError
	default:
		return httpBodyResponseUnauthorized, http.StatusUnauthorized
	}
}
