package server

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"path"
	"reflect"
	"runtime/debug"
	"strings"

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

type HoustonServerWithHandler interface {
	HoustonServer
	RegisterHandlers(serveMux gocommon.ServerMuxInterface, prefix string) error
}

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

func NewHoustonHandler(apiToken string) 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,
		maingateApiToken: apiToken,
	}
}

func (h *houstonHandler) RegisterHandlers(serveMux gocommon.ServerMuxInterface, 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
	}

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

	fsx := http.FileServer(http.Dir(h.deployPath))
	deployPrefix := fmt.Sprintf("%s/%s/", prefix, sub_folder_name_deploys)
	logger.Printf("houstonHandler registed. deployPath : %s -> %s", fmt.Sprintf("%s/%s/", prefix, sub_folder_name_deploys), h.deployPath)
	serveMux.HandleFunc(fmt.Sprintf("%s/%s/", prefix, sub_folder_name_deploys), func(w http.ResponseWriter, r *http.Request) {
		p := strings.TrimPrefix(r.URL.Path, deployPrefix)
		rp := strings.TrimPrefix(r.URL.RawPath, deployPrefix)

		h := md5.New()
		src := strings.TrimLeft(r.URL.Path, fmt.Sprintf("/%s/", prefix))

		h.Write([]byte(src))
		at := hex.EncodeToString(h.Sum(nil))

		if len(p) < len(r.URL.Path) && (r.URL.RawPath == "" || len(rp) < len(r.URL.RawPath)) && at == r.Header.Get("As-X-UrlHash") {
			r2 := new(http.Request)
			*r2 = *r
			r2.URL = new(url.URL)
			*r2.URL = *r.URL
			r2.URL.Path = p
			r2.URL.RawPath = rp
			fsx.ServeHTTP(w, r2)
		} else {
			http.NotFound(w, r)
		}
	})

	// config는 접근하기 편하게 단축 경로 제공
	serveMux.HandleFunc("/config/", func(w http.ResponseWriter, r *http.Request) {
		logger.Println("config url.path :", r.URL.Path)
		testhash := md5.New()
		testhash.Write([]byte(r.URL.Path))
		at := hex.EncodeToString(testhash.Sum(nil))
		hash := r.Header.Get("As-X-UrlHash")
		logger.Println("config at = hash :", at, hash)
		if at == hash {
			urlpath := strings.TrimPrefix(r.URL.Path, "/config/")
			dir := path.Dir(urlpath)
			file := path.Base(urlpath)
			sourceFile := path.Join(h.deployPath, dir, "config", file)
			logger.Println("config dest :", sourceFile)
			bt, err := os.ReadFile(sourceFile)
			if err != nil && !os.IsExist(err) {
				logger.Println("config file is missing :", sourceFile)
				w.WriteHeader(http.StatusNotFound)
			} else {
				if _, err = w.Write(bt); err != nil {
					logger.Println("config write failed :", err)
					w.WriteHeader(http.StatusInternalServerError)
				}
			}
		} else {
			http.NotFound(w, r)
		}
	})

	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.Error(s)
			}
			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 {
			filepath := path.Join(dir, filename)
			// filepath가 이미 있으면 append
			localfile, _ := os.OpenFile(filepath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
			logger.Println("file uploaded :", localfile)
			if localfile != nil {
				defer localfile.Close()
				if _, err = io.Copy(localfile, r.Body); err != nil {
					w.WriteHeader(http.StatusInternalServerError)
				}
			} else {
				w.WriteHeader(http.StatusInternalServerError)
			}
		} else {
			w.WriteHeader(http.StatusInternalServerError)
		}
	})

	return nil
}

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 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)
}
