package rest_http

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

	mjson "git.ali33.ru/fcg-xvii/go-tools/json"
	"git.ali33.ru/fcg-xvii/rest"
	jwt "github.com/dgrijalva/jwt-go"
)

func New(app rest.IApplication, core any, responseHeaders map[string]string) *Rest {
	return &Rest{
		app:             app,
		core:            core,
		responseHeaders: responseHeaders,
	}
}

type Rest struct {
	server          rest.IServer
	app             rest.IApplication
	core            any
	responseHeaders map[string]string
}

func (s *Rest) App() rest.IApplication {
	return s.app
}

func (s *Rest) Prepare(server rest.IServer, httpPrefix string) {
	server.HandleFunc(httpPrefix, s.handle)
	s.server = server
}

func responseNotFound(w http.ResponseWriter) {
	w.WriteHeader(404)
}

func responseError(w http.ResponseWriter, err rest.IErrorArgs, code int) {
	w.WriteHeader(code)
	w.Write([]byte(err.Error()))
}

// handle
func (s *Rest) handle(w http.ResponseWriter, r *http.Request) {
	// установка заголовков ответа
	for k, v := range s.responseHeaders {
		w.Header().Set(k, v)
	}

	// запросы prelight
	if r.Method == "OPTIONS" {
		return
	}

	// Инициализация rest Request
	rr := &RequestIn{
		RequestIn: &rest.RequestIn{
			IRequest: &rest.Request{
				Type:    rest.RequestTypeIn,
				Command: r.URL.Path,
				Data:    mjson.Map{},
				Files:   make(rest.RequestFiles),
			},
			GeneratorToken: s.server.TokenGenerate,
			Core:           s.core,
		},
	}
	// Парсим Bearer токен и извлекаем claims
	authHeader := r.Header.Get("Authorization")
	if authHeader != "" {
		if parts := strings.Split(authHeader, " "); len(parts) == 2 && parts[0] == "Bearer" {
			tokenString := parts[1]
			token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
				if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
					return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
				}
				return s.server.Secret(), nil
			})

			if err != nil {
				log.Printf("Failed to parse JWT: %s", err)
				http.Error(w, "Invalid token", http.StatusUnauthorized)
				return
			}

			if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
				rr.auth = mjson.Map(claims)
			}
		}
	}

	// Если это многокомпонентный запрос, обрабатываем файлы
	if strings.Index(r.Header.Get("Content-Type"), "multipart/form-data") == 0 {
		err := r.ParseMultipartForm(32 << 20) // max memory 32MB, после этого файлы будут сохранены во временных файлах
		log.Println(err)
		if err != nil {
			responseError(w, rest.ErrorMessage("ErrMultipartParse", err.Error()), 500)
			return
		}
		multiPartForm := r.MultipartForm

		data, check := multiPartForm.Value["data"]
		if check {
			rData := rr.RData()
			err := json.NewDecoder(bytes.NewBuffer([]byte(data[0]))).Decode(&rData)
			if err != nil {
				responseError(w, rest.ErrorMessage("ErrMultipartDataParse", err.Error()), 500)
				return
			}
		}

		files := rr.RFiles()
		for filename, headers := range multiPartForm.File {
			for _, header := range headers {
				file, err := header.Open()
				if err != nil {
					err := rest.NewError(
						"ErrMultipartFileParse",
						mjson.Map{
							"filename": filename,
							"message":  err.Error(),
						},
					)
					responseError(w, err, 500)
				}
				rlFile := rest.NewReadCloserLen(
					file,
					header.Size,
				)
				files[filename] = rlFile
			}
		}
		defer rr.RClose()
	} else {
		data := rr.RData()
		err := json.NewDecoder(r.Body).Decode(&data)
		if err != nil {
			responseError(w, rest.ErrorMessage("ErrDataParse", err.Error()), 500)
			return
		}
	}

	// get command

	//command, check := s.commands.GetCommand(r.URL.Path)
	command, check := s.app.Executer(rr)
	if !check {
		responseNotFound(w)
		return
	}

	//log.Println(rr.RData)
	// serialize
	if err := rest.Serialize(rr.RData(), command); err != nil {
		responseError(w, err, 500)
		return
	}

	// validate
	if validator, check := command.(rest.IValidator); check {
		resp := validator.Validate(rr)
		if resp != nil {
			if err := resp.Write(w); err != nil {
				responseError(w, err, 500)
			}
			return
		}
	}

	// execute
	resp := command.Execute(rr)
	if err := resp.Write(w); err != nil {
		responseError(w, err, 500)
	}
	resp.RClose()
}