package handlers

import (
	"io"
	"log"
	"net"
	"net/http"
	"net/url"
	"strings"

	"alpine-router/mihomo"
)

func getMihomoAPIBase() string {
	cfg, err := mihomo.LoadConfig()
	if err != nil {
		return "http://127.0.0.1:9090"
	}
	ec, _ := cfg["external-controller"].(string)
	if ec == "" {
		ec = "0.0.0.0:9090"
	}
	if strings.HasPrefix(ec, "0.0.0.0:") || strings.HasPrefix(ec, ":") {
		ec = "127.0.0.1" + strings.TrimPrefix(ec, "0.0.0.0")
	}
	if !strings.HasPrefix(ec, "http") {
		ec = "http://" + ec
	}
	return ec
}

func getMihomoSecret() string {
	cfg, err := mihomo.LoadConfig()
	if err != nil {
		return ""
	}
	s, _ := cfg["secret"].(string)
	return s
}

func getMihomoHostPort() (string, string) {
	u, err := url.Parse(getMihomoAPIBase())
	if err != nil {
		return "127.0.0.1", "9090"
	}
	host := u.Hostname()
	if host == "0.0.0.0" || host == "" {
		host = "127.0.0.1"
	}
	port := u.Port()
	if port == "" {
		if u.Scheme == "https" {
			port = "443"
		} else {
			port = "80"
		}
	}
	return host, port
}

func HandleMihomoAPIProxy(w http.ResponseWriter, r *http.Request) {
	base := getMihomoAPIBase()
	secret := getMihomoSecret()

	suffix := strings.TrimPrefix(r.URL.Path, "/api/mihomo/api/")
	target, err := url.Parse(base + "/" + suffix)
	if err != nil {
		fail(w, http.StatusInternalServerError, "parse mihomo url: "+err.Error())
		return
	}

	q := r.URL.Query()
	if r.Method == http.MethodGet {
		q.Set("nonce", "1")
	}
	target.RawQuery = q.Encode()

	proxyReq, err := http.NewRequest(r.Method, target.String(), r.Body)
	if err != nil {
		fail(w, http.StatusInternalServerError, "create proxy request: "+err.Error())
		return
	}

	for k, vv := range r.Header {
		if strings.EqualFold(k, "Authorization") {
			continue
		}
		for _, v := range vv {
			proxyReq.Header.Add(k, v)
		}
	}
	if secret != "" {
		proxyReq.Header.Set("Authorization", "Bearer "+secret)
	}

	client := &http.Client{}
	resp, err := client.Do(proxyReq)
	if err != nil {
		fail(w, http.StatusBadGateway, "mihomo api unreachable: "+err.Error())
		return
	}
	defer resp.Body.Close()

	for k, vv := range resp.Header {
		if strings.EqualFold(k, "Content-Length") {
			continue
		}
		for _, v := range vv {
			w.Header().Add(k, v)
		}
	}

	w.WriteHeader(resp.StatusCode)
	_, err = io.Copy(w, resp.Body)
	if err != nil {
		log.Printf("proxy copy error: %v", err)
	}
}

func HandleMihomoWSProxy(w http.ResponseWriter, r *http.Request) {
	secret := getMihomoSecret()
	host, port := getMihomoHostPort()

	suffix := strings.TrimPrefix(r.URL.Path, "/api/mihomo/ws/")
	path := "/" + suffix
	if r.URL.RawQuery != "" {
		path += "?" + r.URL.RawQuery
	}

	hj, ok := w.(http.Hijacker)
	if !ok {
		fail(w, http.StatusInternalServerError, "websocket hijack not supported")
		return
	}

	clientConn, _, err := hj.Hijack()
	if err != nil {
		fail(w, http.StatusInternalServerError, "hijack failed: "+err.Error())
		return
	}

	remoteConn, err := net.Dial("tcp", net.JoinHostPort(host, port))
	if err != nil {
		fail(w, http.StatusBadGateway, "mihomo ws connect failed: "+err.Error())
		clientConn.Close()
		return
	}

	upgradeReq := "GET " + path + " HTTP/1.1\r\n"
	upgradeReq += "Host: " + host + ":" + port + "\r\n"
	upgradeReq += "Upgrade: websocket\r\n"
	upgradeReq += "Connection: Upgrade\r\n"
	upgradeReq += "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
	upgradeReq += "Sec-WebSocket-Version: 13\r\n"
	if secret != "" {
		upgradeReq += "Authorization: Bearer " + secret + "\r\n"
	}
	for k, vv := range r.Header {
		if strings.EqualFold(k, "Upgrade") || strings.EqualFold(k, "Connection") ||
			strings.EqualFold(k, "Sec-Websocket-Key") || strings.EqualFold(k, "Sec-Websocket-Version") ||
			strings.EqualFold(k, "Sec-Websocket-Extensions") || strings.EqualFold(k, "Sec-Websocket-Protocol") ||
			strings.EqualFold(k, "Authorization") {
			continue
		}
		for _, v := range vv {
			upgradeReq += k + ": " + v + "\r\n"
		}
	}
	for k, v := range r.Header {
		if strings.EqualFold(k, "Sec-WebSocket-Protocol") {
			upgradeReq += "Sec-WebSocket-Protocol: " + strings.Join(v, ", ") + "\r\n"
		}
		if strings.EqualFold(k, "Sec-WebSocket-Extensions") {
			upgradeReq += "Sec-WebSocket-Extensions: " + strings.Join(v, ", ") + "\r\n"
		}
	}
	upgradeReq += "\r\n"

	_, err = remoteConn.Write([]byte(upgradeReq))
	if err != nil {
		clientConn.Close()
		remoteConn.Close()
		return
	}

	buf := make([]byte, 4096)
	n, err := remoteConn.Read(buf)
	if err != nil {
		clientConn.Close()
		remoteConn.Close()
		return
	}

	respStr := string(buf[:n])
	if !strings.Contains(respStr, "101") {
		clientConn.Write(buf[:n])
		clientConn.Close()
		remoteConn.Close()
		return
	}

	headerEnd := strings.Index(respStr, "\r\n\r\n")
	if headerEnd >= 0 {
		clientConn.Write(buf[:n])
	} else {
		clientConn.Write(buf[:n])
	}

	go func() {
		io.Copy(remoteConn, clientConn)
		remoteConn.Close()
	}()
	go func() {
		io.Copy(clientConn, remoteConn)
		clientConn.Close()
	}()
}