/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package http

import (
	"context"
	"encoding/json"
	"math"
	"net"
	"net/http"
	"net/url"
	"strings"
	"time"
)

import (
	"github.com/dubbo-go-pixiu/pixiu-api/pkg/router"
)

import (
	"github.com/apache/dubbo-go-pixiu/pkg/client"
	"github.com/apache/dubbo-go-pixiu/pkg/common/constant"
	"github.com/apache/dubbo-go-pixiu/pkg/logger"
	"github.com/apache/dubbo-go-pixiu/pkg/model"
)

const abortIndex int8 = math.MaxInt8 / 2

// HttpContext http context
type HttpContext struct {
	//Deprecated: waiting to delete
	Index int8
	//Deprecated: waiting to delete
	Filters FilterChain
	Timeout time.Duration
	Ctx     context.Context
	Params  map[string]any

	// localReply Means that the request was interrupted,
	// which may occur in the Decode or Encode stage
	localReply bool
	// statusCode code will be return
	statusCode int
	// localReplyBody: happen error
	localReplyBody []byte
	// the response context will return.
	TargetResp any
	// client call response.
	SourceResp any

	HttpConnectionManager model.HttpConnectionManagerConfig
	Route                 *model.RouteAction
	Api                   *router.API

	Request *http.Request
	Writer  http.ResponseWriter
}

type (
	// ErrResponse err response.
	ErrResponse struct {
		Message string `json:"message"`
	}

	// FilterFunc filter func, filter
	FilterFunc func(c *HttpContext)

	// FilterChain filter chain
	FilterChain []FilterFunc
)

// Deprecated: Next logic for lookup filter
func (hc *HttpContext) Next() {
}

// Reset reset http context
func (hc *HttpContext) Reset() {
	hc.Ctx = nil
	hc.Index = -1
	hc.Filters = []FilterFunc{}
	hc.Route = nil
	hc.Api = nil

	hc.TargetResp = nil
	hc.SourceResp = nil
	hc.statusCode = 0
	hc.localReply = false
	hc.localReplyBody = nil
}

// RouteEntry set route
func (hc *HttpContext) RouteEntry(r *model.RouteAction) {
	hc.Route = r
}

// GetRouteEntry get route
func (hc *HttpContext) GetRouteEntry() *model.RouteAction {
	return hc.Route
}

// AddHeader add header
func (hc *HttpContext) AddHeader(k, v string) {
	hc.Writer.Header().Add(k, v)
}

// GetHeader get header
func (hc *HttpContext) GetHeader(k string) string {
	return hc.Request.Header.Get(k)
}

// AllHeaders  get all headers
func (hc *HttpContext) AllHeaders() http.Header {
	return hc.Request.Header
}

// GetUrl get http request url
func (hc *HttpContext) GetUrl() string {
	return hc.Request.URL.Path
}

func (hc *HttpContext) SetUrl(url string) {
	hc.Request.URL.Path = url
}

// GetMethod get method, POST/GET ...
func (hc *HttpContext) GetMethod() string {
	return hc.Request.Method
}

// GetClientIP get client IP
func (hc *HttpContext) GetClientIP() string {
	xForwardedFor := hc.Request.Header.Get("X-Forwarded-For")
	ip := strings.TrimSpace(strings.Split(xForwardedFor, ",")[0])
	if len(ip) != 0 {
		return ip
	}

	ip = strings.TrimSpace(hc.Request.Header.Get("X-Real-Ip"))
	if len(ip) != 0 {
		return ip
	}

	if ip, _, err := net.SplitHostPort(strings.TrimSpace(hc.Request.RemoteAddr)); err == nil && len(ip) != 0 {
		return ip
	}

	return ""
}

// GetApplicationName get application name
func (hc *HttpContext) GetApplicationName() string {
	if u, err := url.Parse(hc.Request.RequestURI); err == nil {
		return strings.Split(u.Path, "/")[0]
	}

	return ""
}

// SendLocalReply Means that the request was interrupted and UnaryResponse will be sent directly
// Even if it’s currently in to Decode stage
func (hc *HttpContext) SendLocalReply(status int, body []byte) {
	hc.localReply = true
	hc.statusCode = status
	hc.localReplyBody = body
	hc.TargetResp = &client.UnaryResponse{Data: body}
	if json.Valid(body) {
		hc.AddHeader(constant.HeaderKeyContextType, constant.HeaderValueApplicationJson)
	} else {
		hc.AddHeader(constant.HeaderKeyContextType, constant.HeaderValueTextPlain)
	}
	writer := hc.Writer
	writer.WriteHeader(status)
	_, err := writer.Write(body)
	if err != nil {
		logger.Errorf("write fail: %v", err)
	}
}

func (hc *HttpContext) GetLocalReplyBody() []byte {
	return hc.localReplyBody
}

func (hc *HttpContext) GetStatusCode() int {
	return hc.statusCode
}

func (hc *HttpContext) StatusCode(code int) {
	hc.statusCode = code
}

func (hc *HttpContext) LocalReply() bool {
	return hc.localReply
}

// API sets the API to http context
func (hc *HttpContext) API(api router.API) {
	if hc.Timeout > api.Timeout {
		hc.Timeout = api.Timeout
	}
	hc.Api = &api
}

// GetAPI get api
func (hc *HttpContext) GetAPI() *router.API {
	return hc.Api
}

// Deprecated: Abort  filter chain break , filter after the current filter will not executed.
func (hc *HttpContext) Abort() {
	hc.Index = abortIndex
}

// Deprecated: AppendFilterFunc append filter func.
func (hc *HttpContext) AppendFilterFunc(ff ...FilterFunc) {
	for _, v := range ff {
		hc.Filters = append(hc.Filters, v)
	}
}

func (hc *HttpContext) GenerateHash() string {
	req := hc.Request
	return req.Method + "." + req.RequestURI
}
