package gemini

import (
	"bufio"
	"context"
	"crypto/tls"
	"fmt"
	"io"
	"net"

	"tildegit.org/tjp/sliderule/internal"
	"tildegit.org/tjp/sliderule/internal/types"
	"tildegit.org/tjp/sliderule/logging"
)

type server struct {
	internal.Server

	handler types.Handler
}

func (s server) Protocol() string { return "GEMINI" }

// NewServer builds a gemini server.
func NewServer(
	ctx context.Context,
	hostname string,
	network string,
	address string,
	handler types.Handler,
	baseLog logging.Logger,
	tlsConfig *tls.Config,
) (types.Server, error) {
	s := &server{handler: handler}

	hostname = internal.JoinDefaultPort(hostname, "1965")
	address = internal.JoinDefaultPort(address, "1965")

	internalServer, err := internal.NewServer(ctx, hostname, network, address, baseLog, s.handleConn)
	if err != nil {
		return nil, err
	}
	s.Server = internalServer

	s.Listener = tls.NewListener(s.Listener, tlsConfig)

	return s, nil
}

func (s *server) handleConn(conn net.Conn) {
	buf := bufio.NewReader(conn)

	var response *types.Response
	request, err := ParseRequest(buf)
	if err != nil {
		response = BadRequest(err.Error())
	} else {
		request.Server = s
		request.RemoteAddr = conn.RemoteAddr()

		if tlsconn, ok := conn.(*tls.Conn); ok {
			state := tlsconn.ConnectionState()
			request.TLSState = &state
		}

		defer func() {
			if r := recover(); r != nil {
				err := fmt.Errorf("%s", r)
				_ = s.LogError("msg", "panic in handler", "err", err)
				_, _ = io.Copy(conn, NewResponseReader(Failure(err)))
			}
		}()
		response = s.handler.Handle(s.Ctx, request)
		if response == nil {
			response = NotFound("Resource does not exist.")
		}
	}

	defer response.Close()
	_, _ = io.Copy(conn, NewResponseReader(response))
}

// GeminiOnly filters requests down to just those on the gemini:// protocol.
//
// Optionally, it will also allow through titan:// requests.
//
// Filtered requests will be turned away with a 53 response "proxy request refused".
func GeminiOnly(allowTitan bool) types.Middleware {
	return func(inner types.Handler) types.Handler {
		return types.HandlerFunc(func(ctx context.Context, request *types.Request) *types.Response {
			if request.Scheme == "gemini" || (allowTitan && request.Scheme == "titan") {
				return inner.Handle(ctx, request)
			}

			return RefuseProxy("Non-gemini protocol requests are not supported.")
		})
	}
}