package servertest

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

	"github.com/imgproxy/imgproxy/v3"
	"github.com/imgproxy/imgproxy/v3/httpheaders"
	"github.com/imgproxy/imgproxy/v3/logger"
	"github.com/imgproxy/imgproxy/v3/testutil"
)

type TestServer struct {
	Addr     net.Addr
	Shutdown context.CancelFunc
}

// Suite is a test suite for integration tests.
//
// It lazily initializes [imgproxy.Config] and [imgproxy.Imgproxy] when they are accessed.
//
// It provides the [Suite.GET] method that lazily initializes a test imgproxy server
// and performs a GET request against it.
//
// Take note that Suite utilizes SetupSuite and TearDownSuite for setup and cleanup.
// If you define them for your test suite, make sure to call the base methods.
type Suite struct {
	testutil.LazySuite

	TestData *testutil.TestDataProvider

	Config   testutil.LazyObj[*imgproxy.Config]
	Imgproxy testutil.LazyObj[*imgproxy.Imgproxy]
	Server   testutil.LazyObj[*TestServer]
}

func (s *Suite) SetupSuite() {
	// Silence all the logs
	logger.Mute()

	// Initialize test data provider (local test files)
	s.TestData = testutil.NewTestDataProvider(s.T)

	s.Config, _ = testutil.NewLazySuiteObj(s, func() (*imgproxy.Config, error) {
		c := imgproxy.NewDefaultConfig()

		c.Server.Bind = ":0"
		c.Server.DevelopmentErrorsMode = true

		c.Fetcher.Transport.Local.Root = s.TestData.Root()
		c.Fetcher.Transport.HTTP.ClientKeepAliveTimeout = 0

		c.OptionsParser.AllowSecurityOptions = true

		c.Security.MaxSrcResolution = 10 * 1024 * 1024
		c.Security.MaxSrcFileSize = 10 * 1024 * 1024
		c.Security.MaxAnimationFrames = 100
		c.Security.MaxAnimationFrameResolution = 10 * 1024 * 1024

		return &c, nil
	})

	s.Imgproxy, _ = testutil.NewLazySuiteObj(s, func() (*imgproxy.Imgproxy, error) {
		return imgproxy.New(s.T().Context(), s.Config())
	})

	// NOTE: if we used s.T().Context() in startServer, server would have been stopped
	// after the first subtest because s.T().Context() is cancelled after subtest.
	//
	// If resetLazyObjs is not called in SetupSubTest, the server would shutdown
	// and won't restart in the second subtest because lazy obj would not be nil.
	ctx := s.T().Context()

	s.Server, _ = testutil.NewLazySuiteObj(
		s,
		func() (*TestServer, error) {
			return s.startServer(ctx, s.Imgproxy()), nil
		},
		func(s *TestServer) error {
			s.Shutdown()
			return nil
		},
	)
}

func (s *Suite) TearDownSuite() {
	logger.Unmute()
}

// GET performs a GET request to the imageproxy real server
func (s *Suite) GET(path string, header ...http.Header) *http.Response {
	url := fmt.Sprintf("http://%s%s", s.Server().Addr, path)

	// Perform GET request to an url
	req, err := http.NewRequest(http.MethodGet, url, nil)
	s.Require().NoError(err)

	// Copy headers from the provided http.Header to the request
	for _, h := range header {
		httpheaders.CopyAll(h, req.Header, true)
	}

	// Do the request
	resp, err := http.DefaultClient.Do(req)
	s.Require().NoError(err)

	return resp
}

// startServer starts imgproxy instance's server for the tests.
// Returns [TestServer] that contains the server address and shutdown function
func (s *Suite) startServer(ctx context.Context, i *imgproxy.Imgproxy) *TestServer {
	//nolint:gosec // TestServer will deal with cancellation
	ctx, cancel := context.WithCancel(ctx)

	addrCh := make(chan net.Addr)

	go func() {
		err := i.StartServer(ctx, addrCh)
		if err != nil {
			s.T().Errorf("Imgproxy stopped with error: %v", err)
		}
	}()

	return &TestServer{
		Addr:     <-addrCh,
		Shutdown: cancel,
	}
}
