package svc

import (
	"fmt"
	"net/http"

	"git.simanc.com/software/golib/v4/features/mo"
	"git.simanc.com/software/golib/v4/gio"
	"git.simanc.com/software/golib/v4/gnet"
	"git.simanc.com/software/golib/v4/infra/ii"
)

const (
	// Method Post
	cmdInsertOne  = "insertOne"
	cmdInsertMany = "insertMany"
	cmdUpdateOne  = "updateOne"
	cmdUpdateMany = "updateMany"
	cmdUpdateById = "updateById"
	cmdFindOne    = "findOne"
	cmdFind       = "find"
	cmdCount      = "count"
	cmdDeleteOne  = "deleteOne"
	cmdDeleteMany = "deleteMany"
)

var (
	actionMap = map[string]struct{}{
		cmdInsertOne:  {},
		cmdInsertMany: {},
		cmdUpdateOne:  {},
		cmdUpdateMany: {},
		cmdUpdateById: {},
		cmdDeleteOne:  {},
		cmdDeleteMany: {},
		cmdFind:       {},
		cmdFindOne:    {},
		cmdCount:      {},
	}
)

const (
	HTTPMaxRequestSize = 4096
)

// action: insertOne/insertMany/updateOne/updateMany/deleteOne/deleteMany/find/findOne
// Request: {"action":"insert", "itemName":"test.test", "fields": {"name":"xiaoming","age":3.1415}}
// Response: {"action":"insert", "itemName": "test.test", "ret":"success", "result":"","fields":{"name":"required"}}

type httpHandleBody struct {
	CMD      string  `json:"cmd"` // CMD 本次请求需要执行的命令
	ItemName ii.Name `json:"itemName"`
	Data     any     `json:"data"` // Data 数据类型根据 action 变化
	ExtData  any     `json:"extData"`
}

type HttpHandler struct {
	Items       ii.Items
	User        ii.User
	RequestSize int64
}

func (f *HttpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	cmd, itemName, err := splitPATH(r.URL.Path, "svc")
	if err != nil {
		http.Error(w, err.Error(), http.StatusForbidden)
		return
	}
	if _, ok := actionMap[cmd]; !ok {
		http.Error(w, "unknown cmd", http.StatusNotFound)
		return
	}
	if _, ok := f.Items.Has(itemName); !ok {
		http.Error(w, ErrItemNotfound.Error(), http.StatusNotFound)
		return
	}
	if f.RequestSize <= 0 {
		f.RequestSize = HTTPMaxRequestSize
	}
	b, err := gio.ReadLimit(r.Body, f.RequestSize)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	var hrb httpHandleBody
	if err = mo.UnmarshalExtJSON(b, true, true); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	hrb.ItemName = itemName
	hrb.CMD = cmd
	switch hrb.CMD {
	case cmdInsertOne:
		f.handleInsertOne(w, &hrb)
	case cmdInsertMany:
		f.handleInsertMany(w, &hrb)
	case cmdUpdateOne:
		f.handleUpdateOne(w, &hrb)
	case cmdUpdateMany:
		f.handleUpdateMany(w, &hrb)
	case cmdUpdateById:
		f.handleUpdateByID(w, &hrb)
	case cmdDeleteOne:
		f.handleDeleteOne(w, &hrb)
	case cmdDeleteMany:
		f.handleDeleteMany(w, &hrb)
	case cmdFindOne:
		f.handleFindOne(w, &hrb)
	case cmdFind:
		f.handleFind(w, &hrb)
	case cmdCount:
		f.handleCount(w, &hrb)
	}
}

func (f *HttpHandler) handleFind(w http.ResponseWriter, hrb *httpHandleBody) {
	filter, err := f.handleFilterData(hrb.Data)
	if err != nil {
		f.respJsonErr(w, err, http.StatusBadRequest)
		return
	}
	rows, err := With(f.User).Find(hrb.ItemName, filter)
	if err != nil {
		f.respJsonErr(w, err, http.StatusInternalServerError)
		return
	}
	resp := &httpHandleBody{
		CMD:      hrb.CMD,
		ItemName: hrb.ItemName,
		Data:     rows,
	}
	f.respJson(w, resp)
}

func (f *HttpHandler) handleFindOne(w http.ResponseWriter, hrb *httpHandleBody) {
	filter, err := f.handleFilterData(hrb.Data)
	if err != nil {
		f.respJsonErr(w, err, http.StatusBadRequest)
		return
	}
	row, err := With(f.User).FindOne(hrb.ItemName, filter)
	if err != nil {
		f.respJsonErr(w, err, http.StatusInternalServerError)
		return
	}
	resp := &httpHandleBody{
		CMD:      hrb.CMD,
		ItemName: hrb.ItemName,
		Data:     row,
	}
	f.respJson(w, resp)
}

func (f *HttpHandler) handleInsertOne(w http.ResponseWriter, hrb *httpHandleBody) {
	data, ok := hrb.Data.(map[string]any)
	if !ok {
		f.respJsonErr(w, ErrDataError, http.StatusBadRequest)
		return
	}
	oid, err := With(f.User).InsertOne(hrb.ItemName, data)
	if err != nil {
		f.respJsonErr(w, err, http.StatusInternalServerError)
		return
	}
	resp := &httpHandleBody{
		CMD:      hrb.CMD,
		ItemName: hrb.ItemName,
		Data:     oid,
	}
	f.respJson(w, resp)
}

func (f *HttpHandler) handleInsertMany(w http.ResponseWriter, hrb *httpHandleBody) {
	data, ok := hrb.Data.([]any)
	if !ok {
		f.respJsonErr(w, ErrDataError, http.StatusBadRequest)
		return
	}
	oidList, err := With(f.User).InsertMany(hrb.ItemName, data)
	if err != nil {
		f.respJsonErr(w, err, http.StatusInternalServerError)
		return
	}
	resp := &httpHandleBody{
		CMD:      hrb.CMD,
		ItemName: hrb.ItemName,
		Data:     oidList,
	}
	f.respJson(w, resp)
}

func (f *HttpHandler) handleUpdateOne(w http.ResponseWriter, hrb *httpHandleBody) {
	filter, err := f.handleFilterData(hrb.Data)
	if err != nil {
		f.respJsonErr(w, err, http.StatusBadRequest)
		return
	}
	update, err := f.handleUpdateExtData(hrb)
	if err != nil {
		f.respJsonErr(w, err, http.StatusBadRequest)
		return
	}
	if err = With(f.User).UpdateOne(hrb.ItemName, filter, update); err != nil {
		f.respJsonErr(w, err, http.StatusInternalServerError)
		return
	}
	resp := &httpHandleBody{
		CMD:      hrb.CMD,
		ItemName: hrb.ItemName,
		Data:     nil,
	}
	f.respJson(w, resp)
}

func (f *HttpHandler) handleUpdateByID(w http.ResponseWriter, hrb *httpHandleBody) {
	idStr, ok := hrb.Data.(string)
	if !ok {
		f.respJsonErr(w, ErrDataError, http.StatusBadRequest)
		return
	}
	oid, err := mo.ID.From(idStr)
	if err != nil {
		f.respJsonErr(w, ErrDataError, http.StatusBadRequest)
		return
	}
	update, err := f.handleUpdateExtData(hrb)
	if err != nil {
		f.respJsonErr(w, err, http.StatusBadRequest)
		return
	}
	if err = With(f.User).UpdateByID(hrb.ItemName, oid, update); err != nil {
		f.respJsonErr(w, err, http.StatusInternalServerError)
		return
	}
	resp := &httpHandleBody{
		CMD:      hrb.CMD,
		ItemName: hrb.ItemName,
		Data:     nil,
	}
	f.respJson(w, resp)
}

func (f *HttpHandler) handleUpdateMany(w http.ResponseWriter, hrb *httpHandleBody) {
	filter, err := f.handleFilterData(hrb.Data)
	if err != nil {
		f.respJsonErr(w, err, http.StatusBadRequest)
		return
	}
	update, err := f.handleUpdateExtData(hrb)
	if err != nil {
		f.respJsonErr(w, err, http.StatusBadRequest)
		return
	}
	if err = With(f.User).UpdateMany(hrb.ItemName, filter, update); err != nil {
		f.respJsonErr(w, err, http.StatusInternalServerError)
		return
	}
	resp := &httpHandleBody{
		CMD:      hrb.CMD,
		ItemName: hrb.ItemName,
		Data:     nil,
	}
	f.respJson(w, resp)
}

func (f *HttpHandler) handleCount(w http.ResponseWriter, hrb *httpHandleBody) {
	var (
		total int64
		err   error
	)
	if hrb.Data == nil || hrb.Data == "" {
		total, err = With(f.User).EstimatedDocumentCount(hrb.ItemName)
	} else {
		filter, err := f.handleFilterData(hrb.Data)
		if err != nil {
			f.respJsonErr(w, err, http.StatusBadRequest)
			return
		}
		total, err = With(f.User).CountDocuments(hrb.ItemName, filter)
	}
	if err != nil {
		f.respJsonErr(w, err, http.StatusInternalServerError)
		return
	}
	resp := &httpHandleBody{
		CMD:      hrb.CMD,
		ItemName: hrb.ItemName,
		Data:     total,
	}
	f.respJson(w, resp)
}

func (f *HttpHandler) handleDeleteOne(w http.ResponseWriter, hrb *httpHandleBody) {
	filter, err := f.handleFilterData(hrb.Data)
	if err != nil {
		f.respJsonErr(w, err, http.StatusBadRequest)
		return
	}
	if err = With(f.User).DeleteOne(hrb.ItemName, filter); err != nil {
		f.respJsonErr(w, err, http.StatusInternalServerError)
		return
	}
	resp := &httpHandleBody{
		CMD:      hrb.CMD,
		ItemName: hrb.ItemName,
		Data:     nil,
	}
	f.respJson(w, resp)
}

func (f *HttpHandler) handleDeleteMany(w http.ResponseWriter, hrb *httpHandleBody) {
	filter, err := f.handleFilterData(hrb.Data)
	if err != nil {
		f.respJsonErr(w, err, http.StatusBadRequest)
		return
	}
	if err = With(f.User).DeleteMany(hrb.ItemName, filter); err != nil {
		f.respJsonErr(w, err, http.StatusInternalServerError)
		return
	}
	resp := &httpHandleBody{
		CMD:      hrb.CMD,
		ItemName: hrb.ItemName,
		Data:     nil,
	}
	f.respJson(w, resp)
}

func (f *HttpHandler) handleUpdateExtData(hrb *httpHandleBody) (mo.Filter, error) {
	switch v := hrb.ExtData.(type) {
	case map[string]any:
		set, err := mo.ToD(v)
		if err != nil {
			return nil, err
		}
		return &mo.Updater{Setter: set}, nil
	default:
		return nil, fmt.Errorf("unsupport data type")
	}
}

func (f *HttpHandler) handleFilterData(data any) (mo.Filter, error) {
	b, err := mo.MarshalExtJSON(data, true, true)
	if err != nil {
		return nil, err
	}
	var filter mo.D
	if err = mo.UnmarshalExtJSON(b, true, &filter); err != nil {
		return nil, err
	}
	return &mo.Matcher{Filter: filter}, nil
}

func (f *HttpHandler) respJson(w http.ResponseWriter, v any) {
	p, err := mo.MarshalExtJSON(v, true, true)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", gnet.HTTPContentTypeJson)
	_, _ = w.Write(p)
}

func (f *HttpHandler) respJsonErr(w http.ResponseWriter, err error, code int) {
	w.Header().Set("Content-Type", gnet.HTTPContentTypeJson)
	w.WriteHeader(code)
	_, _ = w.Write([]byte(fmt.Sprintf(`{"result":"%s"}`, err)))
}
