package server

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"reflect"
	"runtime/debug"
	"strings"

	"repositories.action2quare.com/ayo/gocommon/flagx"
	"repositories.action2quare.com/ayo/gocommon/logger"
)

type HoustonServerWithHandler interface {
	HoustonServer
	RegisterHandlers(serveMux *http.ServeMux, prefix string) error
}

type houstonHandler struct {
	HoustonServer
	methods      map[string]reflect.Method
	deployPath   string
	downloadPath string
}

func NewHoustonHandler() HoustonServerWithHandler {
	var tmp *houstonHandler

	methods := make(map[string]reflect.Method)
	tp := reflect.TypeOf(tmp)
	for i := 0; i < tp.NumMethod(); i++ {
		method := tp.Method(i)
		methods[strings.ToLower(method.Name)] = method
	}
	return &houstonHandler{
		HoustonServer: NewServer(),
		methods:       methods,
	}
}

func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string) error {
	config := loadServerConfig()
	storagePath := config.StorageRoot
	h.deployPath = path.Join(storagePath, sub_folder_name_deploys)
	h.downloadPath = path.Join(storagePath, sub_folder_name_downloads)

	if err := os.MkdirAll(h.deployPath, 0775); err != nil {
		return err
	}

	if err := os.MkdirAll(h.downloadPath, 0775); err != nil {
		return err
	}

	logger.Printf("houstonHandler registed. deployPath : %s, downloadPath : %s", h.deployPath, h.downloadPath)

	if len(prefix) > 0 {
		prefix = "/" + prefix
	}
	serveMux.Handle(prefix, h)

	fsx := http.FileServer(http.Dir(h.deployPath))
	serveMux.Handle(fmt.Sprintf("%s/%s/", prefix, sub_folder_name_deploys), http.StripPrefix(fmt.Sprintf("%s/%s/", prefix, sub_folder_name_deploys), fsx))

	ufsx := http.FileServer(http.Dir(h.downloadPath))
	serveMux.Handle(fmt.Sprintf("%s/%s/", prefix, sub_folder_name_downloads), http.StripPrefix(fmt.Sprintf("%s/%s/", prefix, sub_folder_name_downloads), ufsx))

	serveMux.HandleFunc(fmt.Sprintf("%s/upload", prefix), func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			s := recover()
			if s != nil {
				logger.Println(s)
				debug.PrintStack()
			}
			io.Copy(io.Discard, r.Body)
			r.Body.Close()
		}()

		name := r.Header.Get("Houston-Service-Name")
		version := r.Header.Get("Houston-Service-Version")
		filename := r.Header.Get("Houston-Service-Filename")
		dir := path.Join(h.downloadPath, name, version)
		if err := os.MkdirAll(dir, 0775); err == nil {
			file, _ := os.Create(path.Join(dir, filename))
			if file != nil {
				defer file.Close()
				if _, err = io.Copy(file, r.Body); err != nil {
					w.WriteHeader(http.StatusInternalServerError)
				}
			} else {
				w.WriteHeader(http.StatusInternalServerError)
			}
		} else {
			w.WriteHeader(http.StatusInternalServerError)
		}
	})

	return nil
}

var noauth = flagx.Bool("noauth", false, "")

func (h *houstonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	defer func() {
		s := recover()
		if s != nil {
			logger.Println(s)
			debug.PrintStack()
		}
	}()

	defer func() {
		io.Copy(io.Discard, r.Body)
		r.Body.Close()
	}()

	var userinfo map[string]any
	if !*noauth {
		authheader := r.Header.Get("Authorization")
		if len(authheader) == 0 {
			logger.Println("Authorization header is not valid :", authheader)
			w.WriteHeader(http.StatusBadRequest)
			return
		}

		req, _ := http.NewRequest("GET", "https://graph.microsoft.com/oidc/userinfo", nil)
		req.Header.Add("Authorization", authheader)
		client := &http.Client{}

		resp, err := client.Do(req)
		if err != nil {
			logger.Println("graph microsoft api call failed :", err)
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		defer resp.Body.Close()

		raw, _ := io.ReadAll(resp.Body)
		if err = json.Unmarshal(raw, &userinfo); err != nil {
			return
		}

		if _, expired := userinfo["error"]; expired {
			w.WriteHeader(http.StatusUnauthorized)
			return
		}
	}

	var operation string
	if r.Method == "POST" {
		operation = r.FormValue("operation")
	} else {
		operation = r.URL.Query().Get("operation")
	}

	if len(operation) == 0 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	method, ok := h.methods[strings.ToLower(operation)]
	if !ok {
		// 없는 operation
		logger.Println("fail to call api. operation is not valid :", operation)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	args := []reflect.Value{
		reflect.ValueOf(h),
		reflect.ValueOf(w),
		reflect.ValueOf(r),
	}

	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type")

	method.Func.Call(args)
}
