package browserforwarder

import (
	"bytes"
	"context"
	"io"
	"net/http"
	"strings"
	"time"

	"github.com/v2fly/BrowserBridge/handler"

	"github.com/v2fly/v2ray-core/v5/common"
	"github.com/v2fly/v2ray-core/v5/common/net"
	"github.com/v2fly/v2ray-core/v5/common/platform/securedload"
	"github.com/v2fly/v2ray-core/v5/features/extension"
	"github.com/v2fly/v2ray-core/v5/transport/internet"
)

//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen

type Forwarder struct {
	ctx context.Context

	forwarder  *handler.HTTPHandle
	httpserver *http.Server

	config *Config
}

func (f *Forwarder) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	requestPath := request.URL.Path[1:]

	switch requestPath {
	case "":
		fallthrough
	case "index.js":
		BridgeResource(writer, request, requestPath)
	case "link":
		f.forwarder.ServeBridge(writer, request)
	}
}

func (f *Forwarder) DialWebsocket(url string, header http.Header) (io.ReadWriteCloser, error) {
	protocolHeader := false
	protocolHeaderValue := ""
	unsupportedHeader := false
	for k, v := range header {
		if k == "Sec-Websocket-Protocol" {
			protocolHeader = true
			protocolHeaderValue = v[0]
		} else {
			unsupportedHeader = true
		}
	}
	if unsupportedHeader {
		return nil, newError("unsupported header used, only Sec-Websocket-Protocol is supported for forwarder")
	}
	if !protocolHeader {
		return f.forwarder.Dial(url)
	}
	return f.forwarder.Dial2(url, protocolHeaderValue)
}

func (f *Forwarder) Type() interface{} {
	return extension.BrowserForwarderType()
}

func (f *Forwarder) Start() error {
	if f.config.ListenAddr != "" {
		f.forwarder = handler.NewHttpHandle()
		f.httpserver = &http.Server{Handler: f}

		var listener net.Listener
		var err error
		address := net.ParseAddress(f.config.ListenAddr)

		switch {
		case address.Family().IsIP():
			listener, err = internet.ListenSystem(f.ctx, &net.TCPAddr{IP: address.IP(), Port: int(f.config.ListenPort)}, nil)
		case strings.EqualFold(address.Domain(), "localhost"):
			listener, err = internet.ListenSystem(f.ctx, &net.TCPAddr{IP: net.IP{127, 0, 0, 1}, Port: int(f.config.ListenPort)}, nil)
		default:
			return newError("forwarder cannot listen on the address: ", address)
		}
		if err != nil {
			return newError("forwarder cannot listen on the port ", f.config.ListenPort).Base(err)
		}

		go func() {
			if err := f.httpserver.Serve(listener); err != nil {
				newError("cannot serve http forward server").Base(err).WriteToLog()
			}
		}()
	}
	return nil
}

func (f *Forwarder) Close() error {
	if f.httpserver != nil {
		return f.httpserver.Close()
	}
	return nil
}

func BridgeResource(rw http.ResponseWriter, r *http.Request, path string) {
	content := path
	if content == "" {
		content = "index.html"
	}
	data, err := securedload.GetAssetSecured("browserforwarder/" + content)
	if err != nil {
		err = newError("cannot load necessary resources").Base(err)
		http.Error(rw, err.Error(), http.StatusForbidden)
		return
	}

	http.ServeContent(rw, r, path, time.Now(), bytes.NewReader(data))
}

func NewForwarder(ctx context.Context, cfg *Config) *Forwarder {
	return &Forwarder{config: cfg, ctx: ctx}
}

func init() {
	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {
		return NewForwarder(ctx, cfg.(*Config)), nil
	}))
}
