package v1

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"strings"

	"github.com/yusing/go-proxy/internal/common"
	config "github.com/yusing/go-proxy/internal/config/types"
	"github.com/yusing/go-proxy/internal/gperr"
	"github.com/yusing/go-proxy/internal/net/gphttp"
	"github.com/yusing/go-proxy/internal/net/gphttp/middleware"
	"github.com/yusing/go-proxy/internal/route/provider"
)

type FileType string

const (
	FileTypeConfig     FileType = "config"
	FileTypeProvider   FileType = "provider"
	FileTypeMiddleware FileType = "middleware"
)

func fileType(file string) FileType {
	switch {
	case strings.HasPrefix(path.Base(file), "config."):
		return FileTypeConfig
	case strings.HasPrefix(file, common.MiddlewareComposeBasePath):
		return FileTypeMiddleware
	}
	return FileTypeProvider
}

func (t FileType) IsValid() bool {
	switch t {
	case FileTypeConfig, FileTypeProvider, FileTypeMiddleware:
		return true
	}
	return false
}

func (t FileType) GetPath(filename string) string {
	if t == FileTypeMiddleware {
		return path.Join(common.MiddlewareComposeBasePath, filename)
	}
	return path.Join(common.ConfigBasePath, filename)
}

func getArgs(r *http.Request) (fileType FileType, filename string, err error) {
	fileType = FileType(r.PathValue("type"))
	if !fileType.IsValid() {
		err = fmt.Errorf("invalid file type: %s", fileType)
		return
	}
	filename = r.PathValue("filename")
	if filename == "" {
		err = fmt.Errorf("missing filename")
	}
	return
}

func GetFileContent(w http.ResponseWriter, r *http.Request) {
	fileType, filename, err := getArgs(r)
	if err != nil {
		gphttp.BadRequest(w, err.Error())
		return
	}
	content, err := os.ReadFile(fileType.GetPath(filename))
	if err != nil {
		gphttp.ServerError(w, r, err)
		return
	}
	gphttp.WriteBody(w, content)
}

func validateFile(fileType FileType, content []byte) gperr.Error {
	switch fileType {
	case FileTypeConfig:
		return config.Validate(content)
	case FileTypeMiddleware:
		errs := gperr.NewBuilder("middleware errors")
		middleware.BuildMiddlewaresFromYAML("", content, errs)
		return errs.Error()
	}
	return provider.Validate(content)
}

func ValidateFile(w http.ResponseWriter, r *http.Request) {
	fileType := FileType(r.PathValue("type"))
	if !fileType.IsValid() {
		gphttp.BadRequest(w, "invalid file type")
		return
	}
	content, err := io.ReadAll(r.Body)
	if err != nil {
		gphttp.ServerError(w, r, err)
		return
	}
	r.Body.Close()
	if valErr := validateFile(fileType, content); valErr != nil {
		gphttp.JSONError(w, valErr, http.StatusBadRequest)
		return
	}
	w.WriteHeader(http.StatusOK)
}

func SetFileContent(w http.ResponseWriter, r *http.Request) {
	fileType, filename, err := getArgs(r)
	if err != nil {
		gphttp.BadRequest(w, err.Error())
		return
	}
	content, err := io.ReadAll(r.Body)
	if err != nil {
		gphttp.ServerError(w, r, err)
		return
	}

	if valErr := validateFile(fileType, content); valErr != nil {
		gphttp.JSONError(w, valErr, http.StatusBadRequest)
		return
	}

	err = os.WriteFile(fileType.GetPath(filename), content, 0o644)
	if err != nil {
		gphttp.ServerError(w, r, err)
		return
	}
	w.WriteHeader(http.StatusOK)
}