package middleware

import (
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"strings"

	"github.com/perfect-panel/server/internal/svc"
	pkgaes "github.com/perfect-panel/server/pkg/aes"
	"github.com/perfect-panel/server/pkg/constant"
	"github.com/perfect-panel/server/pkg/result"
	"github.com/perfect-panel/server/pkg/xerr"
	"github.com/pkg/errors"

	"github.com/gin-gonic/gin"
)

const (
	noWritten     = -1
	defaultStatus = http.StatusOK

	ctxDeviceDecryptStatusKey = "device_decrypt_status"
	ctxDeviceDecryptReasonKey = "device_decrypt_reason"
	ctxEncryptedQueryKey      = "encrypted_query"
	ctxDecryptedQueryKey      = "decrypted_query"
	ctxEncryptedBodyKey       = "encrypted_request_body"
	ctxDecryptedBodyKey       = "decrypted_request_body"

	deviceDecryptSkipPathPublicFileUpload = "/v1/public/file/upload"
)

func DeviceMiddleware(srvCtx *svc.ServiceContext) func(c *gin.Context) {
	return func(c *gin.Context) {

		if !srvCtx.Config.Device.Enable {
			c.Set(ctxDeviceDecryptStatusKey, "skipped")
			c.Set(ctxDeviceDecryptReasonKey, "device_encryption_disabled")
			c.Next()
			return
		}

		if srvCtx.Config.Device.SecuritySecret == "" {
			c.Set(ctxDeviceDecryptStatusKey, "failed")
			c.Set(ctxDeviceDecryptReasonKey, "device_secret_empty")
			result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.SecretIsEmpty), "Secret is empty"))
			c.Abort()
			return
		}

		ctx := c.Request.Context()
		if ctx.Value(constant.CtxKeyUser) == nil && c.GetHeader("Login-Type") != "" {
			ctx = context.WithValue(ctx, constant.CtxLoginType, c.GetHeader("Login-Type"))
			c.Request = c.Request.WithContext(ctx)
		}

		loginType, ok := ctx.Value(constant.CtxLoginType).(string)
		if !ok || loginType != "device" {
			c.Set(ctxDeviceDecryptStatusKey, "skipped")
			if ok {
				c.Set(ctxDeviceDecryptReasonKey, fmt.Sprintf("login_type_%s_not_device", loginType))
			} else {
				c.Set(ctxDeviceDecryptReasonKey, "login_type_not_found")
			}
			c.Next()
			return
		}

		rw := NewResponseWriter(c, srvCtx)
		if shouldSkipDeviceRequestDecrypt(c) {
			c.Set(ctxDeviceDecryptStatusKey, "skipped")
			c.Set(ctxDeviceDecryptReasonKey, "multipart_upload_passthrough")
			c.Writer = rw
			c.Next()
			rw.FlushAbort()
			return
		}
		if !rw.Decrypt() {
			c.Set(ctxDeviceDecryptStatusKey, "failed")
			if _, exists := c.Get(ctxDeviceDecryptReasonKey); !exists {
				c.Set(ctxDeviceDecryptReasonKey, "decrypt_failed")
			}
			result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidCiphertext), "Invalid ciphertext"))
			c.Abort()
			return
		}
		c.Writer = rw
		c.Next()
		rw.FlushAbort()
	}
}

func shouldSkipDeviceRequestDecrypt(c *gin.Context) bool {
	if c.Request.URL.Path != deviceDecryptSkipPathPublicFileUpload {
		return false
	}
	return strings.HasPrefix(strings.ToLower(strings.TrimSpace(c.GetHeader("Content-Type"))), "multipart/form-data")
}

func NewResponseWriter(c *gin.Context, srvCtx *svc.ServiceContext) (rw *ResponseWriter) {
	rw = &ResponseWriter{
		c:              c,
		body:           new(bytes.Buffer),
		size:           noWritten,
		status:         defaultStatus,
		ResponseWriter: c.Writer,
	}
	rw.encryptionKey = srvCtx.Config.Device.SecuritySecret
	rw.encryptionMethod = "AES"
	rw.encryption = true
	return rw
}

func (rw *ResponseWriter) Encrypt() {
	if !rw.encryption {
		return
	}
	buf := rw.body.Bytes()
	params := map[string]interface{}{}
	err := json.Unmarshal(buf, &params)
	if err != nil {
		return
	}
	data := params["data"]
	if data != nil {
		var jsonData []byte
		str, ok := data.(string)
		if ok {
			jsonData = []byte(str)
		} else {
			jsonData, _ = json.Marshal(data)
		}
		encrypt, iv, err := pkgaes.Encrypt(jsonData, rw.encryptionKey)
		if err != nil {
			return
		}
		params["data"] = map[string]interface{}{
			"data": encrypt,
			"time": iv,
		}

	}
	marshal, _ := json.Marshal(params)
	rw.body.Reset()
	rw.body.Write(marshal)
}

func (rw *ResponseWriter) Decrypt() bool {
	if !rw.encryption {
		rw.c.Set(ctxDeviceDecryptStatusKey, "skipped")
		rw.c.Set(ctxDeviceDecryptReasonKey, "response_encryption_disabled")
		return true
	}

	//判断url链接中是否存在data和iv数据，存在就进行解密并设置回去
	query := rw.c.Request.URL.Query()
	originalRawQuery := rw.c.Request.URL.RawQuery
	dataStr := query.Get("data")
	timeStr := query.Get("time")
	hasEncryptedQuery := dataStr != "" && timeStr != ""
	queryDecrypted := false
	if hasEncryptedQuery {
		rw.c.Set(ctxEncryptedQueryKey, originalRawQuery)
	}
	if dataStr != "" && timeStr != "" {
		decrypt, err := pkgaes.Decrypt(dataStr, rw.encryptionKey, timeStr)
		if err == nil {
			params := map[string]interface{}{}
			err = json.Unmarshal([]byte(decrypt), &params)
			if err == nil {
				for k, v := range params {
					query.Set(k, fmt.Sprintf("%v", v))
				}
				query.Del("data")
				query.Del("time")
				rw.c.Request.RequestURI = fmt.Sprintf("%s?%s", rw.c.Request.RequestURI[:strings.Index(rw.c.Request.RequestURI, "?")], query.Encode())
				rw.c.Request.URL.RawQuery = query.Encode()
				rw.c.Set(ctxDecryptedQueryKey, query.Encode())
				queryDecrypted = true
			}
		} else {
			rw.c.Set(ctxDeviceDecryptReasonKey, fmt.Sprintf("query_decrypt_failed:%v", err))
		}
	}

	//判断body是否存在数据，存在就尝试解密，并设置回去
	body, err := io.ReadAll(rw.c.Request.Body)
	if err != nil {
		if queryDecrypted {
			rw.c.Set(ctxDeviceDecryptStatusKey, "success")
		} else {
			rw.c.Set(ctxDeviceDecryptStatusKey, "skipped")
		}
		rw.c.Set(ctxDeviceDecryptReasonKey, fmt.Sprintf("read_body_failed:%v", err))
		return true
	}

	if len(body) == 0 {
		if queryDecrypted {
			rw.c.Set(ctxDeviceDecryptStatusKey, "success")
		} else {
			rw.c.Set(ctxDeviceDecryptStatusKey, "skipped")
			if hasEncryptedQuery {
				rw.c.Set(ctxDeviceDecryptReasonKey, "query_decrypt_failed")
			} else {
				rw.c.Set(ctxDeviceDecryptReasonKey, "empty_body")
			}
		}
		return true
	}
	rw.c.Set(ctxEncryptedBodyKey, string(body))

	params := map[string]interface{}{}
	err = json.Unmarshal(body, &params)
	data := params["data"]
	nonce := params["time"]
	if err != nil || data == nil {
		if err != nil {
			rw.c.Set(ctxDeviceDecryptReasonKey, fmt.Sprintf("body_unmarshal_failed:%v", err))
		} else {
			rw.c.Set(ctxDeviceDecryptReasonKey, "body_data_field_missing")
		}
		return false
	}

	str, ok := data.(string)
	if !ok {
		rw.c.Set(ctxDeviceDecryptReasonKey, "body_data_not_string")
		return false
	}
	iv, ok := nonce.(string)
	if !ok {
		rw.c.Set(ctxDeviceDecryptReasonKey, "body_time_not_string")
		return false
	}

	decrypt, err := pkgaes.Decrypt(str, rw.encryptionKey, iv)
	if err != nil {
		rw.c.Set(ctxDeviceDecryptReasonKey, fmt.Sprintf("body_decrypt_failed:%v", err))
		return false
	}
	rw.c.Request.Body = io.NopCloser(bytes.NewBuffer([]byte(decrypt)))
	rw.c.Request.Header.Set("Content-Type", "application/json")
	rw.c.Set(ctxDecryptedBodyKey, decrypt)
	rw.c.Set(ctxDeviceDecryptStatusKey, "success")
	return true
}

func (rw *ResponseWriter) FlushAbort() {
	defer rw.c.Abort()
	rw.flush = true
	if rw.encryption {
		rw.Encrypt()
	}
	_, err := rw.Write(rw.body.Bytes())
	if err != nil {
		return
	}
}

type ResponseWriter struct {
	http.ResponseWriter
	size             int
	status           int
	flush            bool
	body             *bytes.Buffer
	c                *gin.Context
	encryption       bool
	encryptionKey    string
	encryptionMethod string
}

func (rw *ResponseWriter) Unwrap() http.ResponseWriter {
	return rw.ResponseWriter
}

//nolint:unused
func (rw *ResponseWriter) reset(writer http.ResponseWriter) {
	rw.ResponseWriter = writer
	rw.size = noWritten
	rw.status = defaultStatus
}

func (rw *ResponseWriter) WriteHeader(code int) {
	if code > 0 && rw.status != code {
		if rw.Written() {
			return
		}
		rw.status = code
	}
}

func (rw *ResponseWriter) WriteHeaderNow() {
	if !rw.Written() {
		rw.size = 0
		rw.ResponseWriter.WriteHeader(rw.status)
	}
}

func (rw *ResponseWriter) Write(data []byte) (n int, err error) {
	if rw.flush {
		rw.WriteHeaderNow()
		n, err = rw.ResponseWriter.Write(data)
		rw.size += n
	} else {
		rw.body.Write(data)
	}
	return
}

func (rw *ResponseWriter) WriteString(s string) (n int, err error) {
	if rw.flush {
		rw.WriteHeaderNow()
		n, err = rw.ResponseWriter.Write([]byte(s))
		rw.size += n
	} else {
		rw.body.Write([]byte(s))
	}
	return
}

func (rw *ResponseWriter) Status() int {
	return rw.status
}

func (rw *ResponseWriter) Size() int {
	return rw.size
}

func (rw *ResponseWriter) Written() bool {
	return rw.size != noWritten
}

// Hijack implements the http.Hijacker interface.
func (rw *ResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	if rw.size < 0 {
		rw.size = 0
	}
	return rw.ResponseWriter.(http.Hijacker).Hijack()
}

// CloseNotify implements the http.CloseNotifier interface.
func (rw *ResponseWriter) CloseNotify() <-chan bool {
	// 通过 r.Context().Done() 来监听请求的取消
	done := rw.c.Request.Context().Done()
	closed := make(chan bool)

	// 当上下文被取消时，通过 closed channel 发送通知
	go func() {
		<-done
		closed <- true
	}()

	return closed
}

// Flush implements the http.Flusher interface.
func (rw *ResponseWriter) Flush() {
	rw.WriteHeaderNow()
	rw.ResponseWriter.(http.Flusher).Flush()
}

func (rw *ResponseWriter) Pusher() (pusher http.Pusher) {
	if pusher, ok := rw.ResponseWriter.(http.Pusher); ok {
		return pusher
	}
	return nil
}
