package idlewatcher

import (
	"context"
	"fmt"
	"net/http"
	"strconv"

	"github.com/yusing/godoxy/internal/homepage"
	httputils "github.com/yusing/goutils/http"
	"github.com/yusing/goutils/http/httpheaders"

	_ "unsafe"
)

type ForceCacheControl struct {
	expires string
	http.ResponseWriter
}

func (f *ForceCacheControl) WriteHeader(code int) {
	f.ResponseWriter.Header().Set("Cache-Control", "must-revalidate")
	f.ResponseWriter.Header().Set("Expires", f.expires)
	f.ResponseWriter.WriteHeader(code)
}

func (f *ForceCacheControl) Unwrap() http.ResponseWriter {
	return f.ResponseWriter
}

// ServeHTTP implements http.Handler.
func (w *Watcher) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
	shouldNext := w.wakeFromHTTP(rw, r)
	if !shouldNext {
		return
	}
	select {
	case <-r.Context().Done():
		return
	default:
		f := &ForceCacheControl{expires: w.expires().Format(http.TimeFormat), ResponseWriter: rw}
		w.rp.ServeHTTP(f, r)
	}
}

func isFaviconPath(path string) bool {
	return path == "/favicon.ico"
}

func (w *Watcher) redirectToStartEndpoint(rw http.ResponseWriter, r *http.Request) {
	uri := "/"
	if w.cfg.StartEndpoint != "" {
		uri = w.cfg.StartEndpoint
	}
	http.Redirect(rw, r, uri, http.StatusTemporaryRedirect)
}

func (w *Watcher) getFavIcon(ctx context.Context) (result homepage.FetchResult, err error) {
	r := w.route
	hp := r.HomepageItem()
	if hp.Icon != nil {
		if hp.Icon.IconSource == homepage.IconSourceRelative {
			result, err = homepage.FindIcon(ctx, r, *hp.Icon.FullURL)
		} else {
			result, err = homepage.FetchFavIconFromURL(ctx, hp.Icon)
		}
	} else {
		// try extract from "link[rel=icon]"
		result, err = homepage.FindIcon(ctx, r, "/")
	}
	if result.StatusCode == 0 {
		result.StatusCode = http.StatusOK
	}
	return result, err
}

func (w *Watcher) wakeFromHTTP(rw http.ResponseWriter, r *http.Request) (shouldNext bool) {
	w.resetIdleTimer()

	// pass through if container is already ready
	if w.ready() {
		return true
	}

	// handle favicon request
	if isFaviconPath(r.URL.Path) {
		result, err := w.getFavIcon(r.Context())
		if err != nil {
			rw.WriteHeader(result.StatusCode)
			fmt.Fprint(rw, err)
			return false
		}
		rw.Header().Set("Content-Type", result.ContentType())
		rw.WriteHeader(result.StatusCode)
		rw.Write(result.Icon)
		return false
	}

	// Check if start endpoint is configured and request path matches
	if w.cfg.StartEndpoint != "" && r.URL.Path != w.cfg.StartEndpoint {
		http.Error(rw, "Forbidden: Container can only be started via configured start endpoint", http.StatusForbidden)
		return false
	}

	accept := httputils.GetAccept(r.Header)
	acceptHTML := (r.Method == http.MethodGet && accept.AcceptHTML() || r.RequestURI == "/" && accept.IsEmpty())

	isCheckRedirect := r.Header.Get(httpheaders.HeaderGoDoxyCheckRedirect) != ""
	if !isCheckRedirect && acceptHTML {
		// Send a loading response to the client
		body := w.makeLoadingPageBody()
		rw.Header().Set("Content-Type", "text/html; charset=utf-8")
		rw.Header().Set("Content-Length", strconv.Itoa(len(body)))
		rw.Header().Set("Cache-Control", "no-cache")
		rw.Header().Add("Cache-Control", "no-store")
		rw.Header().Add("Cache-Control", "must-revalidate")
		rw.Header().Add("Connection", "close")
		if _, err := rw.Write(body); err != nil {
			return false
		}
		return false
	}

	ctx := r.Context()
	if w.canceled(ctx) {
		w.redirectToStartEndpoint(rw, r)
		return false
	}

	w.l.Trace().Msg("signal received")
	err := w.Wake(ctx)
	if err != nil {
		http.Error(rw, "Internal Server Error", http.StatusInternalServerError)
		httputils.LogError(r).Msg(fmt.Sprintf("failed to wake: %v", err))
		return false
	}

	// Wait for route to be started
	if !w.waitStarted(ctx) {
		return false
	}

	// Wait for container to become ready
	if !w.waitForReady(ctx) {
		if w.canceled(ctx) {
			w.redirectToStartEndpoint(rw, r)
		}
		return false
	}

	if isCheckRedirect {
		w.l.Debug().Stringer("url", w.hc.URL()).Msg("container is ready, redirecting")
		rw.WriteHeader(http.StatusOK)
		return false
	}
	w.l.Debug().Stringer("url", w.hc.URL()).Msg("container is ready, passing through")
	return true
}
