package proxy

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"time"

	"github.com/pocketbase/pocketbase"
	"github.com/pocketbase/pocketbase/core"
	"github.com/pocketbase/pocketbase/tools/router"
)

var Logger *slog.Logger

func ServeProxyPassingToNuxt(app *pocketbase.PocketBase, se *core.ServeEvent) *router.Route[*core.RequestEvent] {
	Logger = app.Logger()

	proxyUrl, _ := app.RootCmd.Flags().GetString("proxy")

	if len(proxyUrl) > 0 {
		target, _ := url.Parse(proxyUrl) // Node.js app

		proxy := httputil.NewSingleHostReverseProxy(target)

		originalDirector := proxy.Director
		proxy.Director = func(req *http.Request) {
			originalDirector(req)
			req.Host = target.Host
		}
		proxy.Transport = &loggingTransport{http.DefaultTransport}

		proxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) {
			if errors.Is(err, context.Canceled) {
				return
			}
			app.Logger().Error(fmt.Sprintf("Proxy error: %v for %s %s", err, req.Method, req.URL.Path), "type", "proxy")
			http.Error(rw, "Proxy error", http.StatusBadGateway)
		}

		return se.Router.Any("/", func(e *core.RequestEvent) error {
			// Ping the backend with a HEAD request (or TCP dial)
			backendUp := isBackendAlive(target)
			if !backendUp {
				app.Logger().Error(fmt.Sprintf("Backend %s is unreachable, sending 502", target), "type", "proxy", "path", e.Request.URL.Path, "remoteIP", e.Request.RemoteAddr)
				e.Response.WriteHeader(http.StatusBadGateway)
				e.Response.Write([]byte("502 Backend is unavailable"))
				return nil
			}

			// Forward to Node.js
			proxy.ServeHTTP(e.Response, e.Request)
			return nil
		})
	} else {
		return nil
	}
}

func isBackendAlive(target *url.URL) bool {
	conn, err := net.DialTimeout("tcp", target.Host, 500*time.Millisecond)
	if err != nil {
		return false
	}
	conn.Close()
	return true
}

type loggingTransport struct {
	rt http.RoundTripper
}

func (t *loggingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	start := time.Now()

	// Do the actual request
	resp, err := t.rt.RoundTrip(req)
	duration := time.Since(start)

	// Prepare fields
	remoteAddr := req.RemoteAddr
	if ip := req.Header.Get("X-Real-IP"); len(ip) > 0 {
		remoteAddr = ip
	}
	method := req.Method
	uri := req.URL.RequestURI()
	// proto := req.Proto
	status := 0
	size := 0

	if resp != nil {
		status = resp.StatusCode
		if resp.ContentLength > 0 {
			size = int(resp.ContentLength)
		}
	}

	referer := req.Referer()
	ua := req.UserAgent()
	// timestamp := time.Now().Format("02/Jan/2006:15:04:05 -0700")

	Logger.Info(
		fmt.Sprintf("%s %s", method, uri),
		"type", "proxy",
		// "method", method,
		// "url", uri,
		"referer", referer,
		"remoteIP", remoteAddr,
		"userAgent", ua,
		"execTime", fmt.Sprintf("%.4fms", float64(duration.Milliseconds())),
		"status", status,
		"size", size,
	)

	return resp, err
}
