package assetserver

import (
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"sync"

	"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
)

type assetServerWebView struct {
	// ExpectedWebViewHost is checked against the Request Host of every WebViewRequest, other hosts won't be processed.
	ExpectedWebViewHost string

	dispatchInit    sync.Once
	dispatchReqC    chan<- webview.Request
	dispatchWorkers int
}

// ServeWebViewRequest processes the HTTP Request asynchronously by faking a golang HTTP Server.
// The request will be finished with a StatusNotImplemented code if no handler has written to the response.
// The AssetServer takes ownership of the request and the caller mustn't close it or access it in any other way.
func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
	d.dispatchInit.Do(func() {
		workers := d.dispatchWorkers
		if workers <= 0 {
			return
		}

		workerC := make(chan webview.Request, workers*2)
		for i := 0; i < workers; i++ {
			go func() {
				for req := range workerC {
					d.processWebViewRequest(req)
				}
			}()
		}

		dispatchC := make(chan webview.Request)
		go queueingDispatcher(50, dispatchC, workerC)

		d.dispatchReqC = dispatchC
	})

	if d.dispatchReqC == nil {
		go d.processWebViewRequest(req)
	} else {
		d.dispatchReqC <- req
	}
}

func (d *AssetServer) processWebViewRequest(r webview.Request) {
	uri, _ := r.URL()
	d.processWebViewRequestInternal(r)
	if err := r.Close(); err != nil {
		d.logError("Unable to call close for request for uri '%s'", uri)
	}
}

// processWebViewRequestInternal processes the HTTP Request by faking a golang HTTP Server.
// The request will be finished with a StatusNotImplemented code if no handler has written to the response.
func (d *AssetServer) processWebViewRequestInternal(r webview.Request) {
	uri := "unknown"
	var err error

	wrw := r.Response()
	defer func() {
		if err := wrw.Finish(); err != nil {
			d.logError("Error finishing request '%s': %s", uri, err)
		}
	}()

	var rw http.ResponseWriter = &contentTypeSniffer{rw: wrw} // Make sure we have a Content-Type sniffer
	defer rw.WriteHeader(http.StatusNotImplemented)           // This is a NOP when a handler has already written and set the status

	uri, err = r.URL()
	if err != nil {
		d.logError("Error processing request, unable to get URL: %s (HttpResponse=500)", err)
		http.Error(rw, err.Error(), http.StatusInternalServerError)
		return
	}

	method, err := r.Method()
	if err != nil {
		d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Method: %w", err))
		return
	}

	header, err := r.Header()
	if err != nil {
		d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Header: %w", err))
		return
	}

	body, err := r.Body()
	if err != nil {
		d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Body: %w", err))
		return
	}

	if body == nil {
		body = http.NoBody
	}
	defer body.Close()

	req, err := http.NewRequest(method, uri, body)
	if err != nil {
		d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Request: %w", err))
		return
	}

	// For server requests, the URL is parsed from the URI supplied on the Request-Line as stored in RequestURI. For
	// most requests, fields other than Path and RawQuery will be empty. (See RFC 7230, Section 5.3)
	req.URL.Scheme = ""
	req.URL.Host = ""
	req.URL.Fragment = ""
	req.URL.RawFragment = ""

	if url := req.URL; req.RequestURI == "" && url != nil {
		req.RequestURI = url.String()
	}

	req.Header = header

	if req.RemoteAddr == "" {
		// 192.0.2.0/24 is "TEST-NET" in RFC 5737
		req.RemoteAddr = "192.0.2.1:1234"
	}

	if req.ContentLength == 0 {
		req.ContentLength = -1
	} else {
		size := strconv.FormatInt(req.ContentLength, 10)
		req.Header.Set(HeaderContentLength, size)
	}

	if host := req.Header.Get(HeaderHost); host != "" {
		req.Host = host
	}

	if expectedHost := d.ExpectedWebViewHost; expectedHost != "" && expectedHost != req.Host {
		d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("expected host '%s' in request, but was '%s'", expectedHost, req.Host))
		return
	}

	d.ServeHTTP(rw, req)
}

func (d *AssetServer) webviewRequestErrorHandler(uri string, rw http.ResponseWriter, err error) {
	logInfo := uri
	if uri, err := url.ParseRequestURI(uri); err == nil {
		logInfo = strings.Replace(logInfo, fmt.Sprintf("%s://%s", uri.Scheme, uri.Host), "", 1)
	}

	d.logError("Error processing request '%s': %s (HttpResponse=500)", logInfo, err)
	http.Error(rw, err.Error(), http.StatusInternalServerError)
}

func queueingDispatcher[T any](minQueueSize uint, inC <-chan T, outC chan<- T) {
	q := newRingqueue[T](minQueueSize)
	for {
		in, ok := <-inC
		if !ok {
			return
		}

		q.Add(in)
		for q.Len() != 0 {
			out, _ := q.Peek()
			select {
			case outC <- out:
				q.Remove()
			case in, ok := <-inC:
				if !ok {
					return
				}

				q.Add(in)
			}
		}
	}
}
