package runner

import (
	"context"
	"errors"
	"net"
	"net/http"

	ogrpc "github.com/opencloud-eu/opencloud/pkg/service/grpc"
	ohttp "github.com/opencloud-eu/opencloud/pkg/service/http"
	"github.com/opencloud-eu/reva/v2/cmd/revad/runtime"
	"google.golang.org/grpc"
)

// NewGoMicroGrpcServerRunner creates a new runner based on the provided go-micro's
// GRPC service. The service is expected to be created via
// "github.com/opencloud-eu/opencloud/pkg/service/grpc".NewService(...) function
//
// The runner will behave as described:
// * The task is to start a server and listen for connections. If the server
// can't start, the task will finish with that error.
// * The stopper will call the server's stop method and send the result to
// the task.
// * The stopper will run asynchronously because the stop method could take a
// while and we don't want to block
func NewGoMicroGrpcServerRunner(name string, server ogrpc.Service, opts ...Option) *Runner {
	httpCh := make(chan error, 1)
	r := New(name, func() error {
		// start the server and return if it fails
		if err := server.Server().Start(); err != nil {
			return err
		}
		return <-httpCh // wait for the result
	}, func() {
		// stop implies deregistering and waiting for request to finish,
		// so don't block
		go func() {
			httpCh <- server.Server().Stop() // stop and send result through channel
			close(httpCh)
		}()
	}, opts...)
	return r
}

// NewGoMicroHttpServerRunner creates a new runner based on the provided go-micro's
// HTTP service. The service is expected to be created via
// "github.com/opencloud-eu/opencloud/pkg/service/http".NewService(...) function
//
// The runner will behave as described:
// * The task is to start a server and listen for connections. If the server
// can't start, the task will finish with that error.
// * The stopper will call the server's stop method and send the result to
// the task.
// * The stopper will run asynchronously because the stop method could take a
// while and we don't want to block
func NewGoMicroHttpServerRunner(name string, server ohttp.Service, opts ...Option) *Runner {
	httpCh := make(chan error, 1)
	r := New(name, func() error {
		// start the server and return if it fails
		if err := server.Server().Start(); err != nil {
			return err
		}
		return <-httpCh // wait for the result
	}, func() {
		// stop implies deregistering and waiting for request to finish,
		// so don't block
		go func() {
			httpCh <- server.Server().Stop() // stop and send result through channel
			close(httpCh)
		}()
	}, opts...)
	return r
}

// NewGolangHttpServerRunner creates a new runner based on the provided HTTP server.
// The HTTP server is expected to be created via
// "github.com/opencloud-eu/opencloud/pkg/service/debug".NewService(...) function
// and it's expected to be a regular golang HTTP server
//
// The runner will behave as described:
// * The task starts a server and listen for connections. If the server
// can't start, the task will finish with that error. If the server is shutdown
// the task will wait for the shutdown to return that result (task won't finish
// immediately, but wait until shutdown returns)
// * The stopper will call the server's shutdown method and send the result to
// the task. The stopper will wait up to 5 secs for the shutdown.
// * The stopper will run asynchronously because the shutdown could take a
// while and we don't want to block
func NewGolangHttpServerRunner(name string, server *http.Server, opts ...Option) *Runner {
	debugCh := make(chan error, 1)
	r := New(name, func() error {
		// start listening and return if the error is NOT ErrServerClosed.
		// ListenAndServe will always return a non-nil error.
		// We need to wait and get the result of the Shutdown call.
		// App shouldn't exit until Shutdown has returned.
		if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			return err
		}
		// wait for the shutdown and return the result
		return <-debugCh
	}, func() {
		// Since Shutdown might take some time, don't block
		go func() {
			// Use the DefaultInterruptDuration or InterruptDuration as the shutdown timeout.
			shutdownCtx, cancel := context.WithTimeout(context.Background(), DefaultInterruptDuration)
			defer cancel()

			debugCh <- server.Shutdown(shutdownCtx)
			close(debugCh)
		}()
	}, opts...)

	return r
}

// NewGolangGrpcServerRunner creates a new runner based on the provided GRPC
// server. The GRPC server is expected to be a regular golang GRPC server,
// created via "google.golang.org/grpc".NewServer(...)
// A listener also needs to be provided for the server to listen there.
//
// The runner will just start the GRPC server in the listener, and the server
// will be gracefully stopped when interrupted
func NewGolangGrpcServerRunner(name string, server *grpc.Server, listener net.Listener, opts ...Option) *Runner {
	r := New(name, func() error {
		return server.Serve(listener)
	}, func() {
		// Since GracefulStop might take some time, don't block
		go func() {
			server.GracefulStop()
		}()
	}, opts...)

	return r
}

// NewRevaServiceRunner creates a new runner based on the provided reva RevaDrivenServer
// The runner will behave as described:
// * The task is to start a server and listen for connections. If the server
// can't start, the task will finish with that error.
// * The stopper will call the server's stop method and send the result to
// the task.
// * The stopper will run asynchronously because the stop method could take a
// while and we don't want to block
func NewRevaServiceRunner(name string, server runtime.RevaDrivenServer, opts ...Option) *Runner {
	httpCh := make(chan error, 1)
	r := New(name, func() error {
		// start the server and return if it fails
		if err := server.Start(); err != nil {
			return err
		}
		return <-httpCh // wait for the result
	}, func() {
		// stop implies deregistering and waiting for the request to finish,
		// so don't block
		go func() {
			httpCh <- server.Stop() // stop and send a result through a channel
			close(httpCh)
		}()
	}, opts...)

	return r
}
