package server

import (
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"

	"github.com/status-im/status-go/images"
	"github.com/status-im/status-go/protocol/common"
	"github.com/status-im/status-go/server/servertest"
)

const (
	waitTime            = 50 * time.Millisecond
	customPortForTests  = 1337
	defaultPortForTests = 80
)

var (
	baseURL                = "https://127.0.0.1"
	baseURLWithCustomPort  = fmt.Sprintf("%s:%d", baseURL, customPortForTests)
	baseURLWithDefaultPort = fmt.Sprintf("%s:%d", baseURL, defaultPortForTests)
)

func TestServerURLSuite(t *testing.T) {
	suite.Run(t, new(ServerURLSuite))
}

type ServerURLSuite struct {
	suite.Suite
	servertest.TestKeyComponents
	servertest.TestLoggerComponents

	server       *MediaServer
	serverForQR  *MediaServer
	serverNoPort *MediaServer
	testStart    time.Time
}

func (s *ServerURLSuite) SetupTest() {
	s.SetupKeyComponents(s.T())
	s.SetupLoggerComponents()

	mediaServer, err := NewMediaServer(nil, nil, nil, nil)
	s.Require().NoError(err)

	s.serverForQR = mediaServer

	err = s.serverForQR.Start()
	s.Require().NoError(err)

	s.server = &MediaServer{Server: Server{
		hostname:   LocalHostIP.String(),
		portManger: newPortManager(s.Logger, nil),
	}}
	err = s.server.SetPort(customPortForTests)
	s.Require().NoError(err)

	s.serverNoPort = &MediaServer{Server: Server{
		hostname:   LocalHostIP.String(),
		portManger: newPortManager(s.Logger, nil),
	}}
	go func() {
		time.Sleep(waitTime)
		s.serverNoPort.port = defaultPortForTests
	}()

	s.testStart = time.Now()
}

// testNoPort takes two strings and compares expects them both to be equal
// then compares ServerURLSuite.testStart to the current time
// the difference must be greater than waitTime.
// This is caused by the ServerURLSuite.SetupTest waiting waitTime before unlocking the portWait sync.Mutex
func (s *ServerURLSuite) testNoPort(expected string, actual string) {
	s.Require().Equal(expected, actual)
	s.Require().Greater(time.Since(s.testStart), waitTime)
}

func (s *ServerURLSuite) TestServer_MakeBaseURL() {
	s.Require().Equal(baseURLWithCustomPort, s.server.MakeBaseURL().String())
	s.testNoPort(baseURLWithDefaultPort, s.serverNoPort.MakeBaseURL().String())
}

func (s *ServerURLSuite) TestServer_MakeImageServerURL() {
	s.Require().Equal(baseURLWithCustomPort+"/messages/", s.server.MakeImageServerURL())
	s.testNoPort(baseURLWithDefaultPort+"/messages/", s.serverNoPort.MakeImageServerURL())
}

func (s *ServerURLSuite) TestServer_MakeImageURL() {
	s.Require().Equal(
		baseURLWithCustomPort+"/messages/images?messageId=0x10aded70ffee",
		s.server.MakeImageURL("0x10aded70ffee"))

	s.testNoPort(
		baseURLWithDefaultPort+"/messages/images?messageId=0x10aded70ffee",
		s.serverNoPort.MakeImageURL("0x10aded70ffee"))
}

func (s *ServerURLSuite) TestServer_MakeLinkPreviewThumbnailURL() {
	s.Require().Equal(
		baseURLWithCustomPort+"/link-preview/thumbnail?message-id=99&url=https%3A%2F%2Fgithub.com",
		s.server.MakeLinkPreviewThumbnailURL("99", "https://github.com"))

	s.testNoPort(
		baseURLWithDefaultPort+"/link-preview/thumbnail?message-id=99&url=https%3A%2F%2Fgithub.com",
		s.serverNoPort.MakeLinkPreviewThumbnailURL("99", "https://github.com"))
}

func (s *ServerURLSuite) TestServer_MakeStatusLinkPreviewThumbnailURL() {
	s.Require().Equal(
		baseURLWithCustomPort+"/status-link-preview/thumbnail?image-id=contact-icon&message-id=99&url=https%3A%2F%2Fstatus.app",
		s.server.MakeStatusLinkPreviewThumbnailURL("99", "https://status.app", common.MediaServerContactIcon))

	s.testNoPort(
		baseURLWithDefaultPort+"/status-link-preview/thumbnail?image-id=contact-icon&message-id=99&url=https%3A%2F%2Fstatus.app",
		s.serverNoPort.MakeStatusLinkPreviewThumbnailURL("99", "https://status.app", common.MediaServerContactIcon))
}

func (s *ServerURLSuite) TestServer_MakeAudioURL() {
	s.Require().Equal(
		baseURLWithCustomPort+"/messages/audio?messageId=0xde1e7ebee71e",
		s.server.MakeAudioURL("0xde1e7ebee71e"))
	s.testNoPort(
		baseURLWithDefaultPort+"/messages/audio?messageId=0xde1e7ebee71e",
		s.serverNoPort.MakeAudioURL("0xde1e7ebee71e"))
}

func (s *ServerURLSuite) TestServer_MakeStickerURL() {
	s.Require().Equal(
		baseURLWithCustomPort+"/ipfs?hash=0xdeadbeef4ac0",
		s.server.MakeStickerURL("0xdeadbeef4ac0"))
	s.testNoPort(
		baseURLWithDefaultPort+"/ipfs?hash=0xdeadbeef4ac0",
		s.serverNoPort.MakeStickerURL("0xdeadbeef4ac0"))
}

// TestQRCodeGeneration tests if we provide all the correct parameters to the media server
// do we get a valid QR code or not as part of the response payload.
// we have stored a generated QR code in tests folder, and we compare their bytes.
func (s *ServerURLSuite) TestQRCodeGeneration() {

	qrURL := "https://github.com/status-im/status-go/pull/3154"
	generatedURL := base64.StdEncoding.EncodeToString([]byte(qrURL))
	generatedURL = s.serverForQR.MakeQRURL(generatedURL, "false", "2", "200", "", "")

	u, err := url.Parse(generatedURL)
	if err != nil {
		s.Require().NoError(err)
	}

	if u.Scheme == "" || u.Host == "" {
		s.Require().Failf("generatedURL is not a valid URL: %s", generatedURL)
	}

	serverCert := s.serverForQR.cert
	serverCertBytes := serverCert.Certificate[0]

	certPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: serverCertBytes})

	rootCAs, err := x509.SystemCertPool()
	if err != nil {
		s.Require().NoError(err)
	}

	_ = rootCAs.AppendCertsFromPEM(certPem)
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{
			MinVersion: tls.VersionTLS12,
			RootCAs:    rootCAs,
		},
	}

	client := &http.Client{Transport: tr}

	req, err := http.NewRequest(http.MethodGet, generatedURL, nil)
	if err != nil {
		s.Require().NoError(err)
	}

	resp, err := client.Do(req)
	if err != nil {
		s.Require().NoError(err)
	}

	defer func() {
		_ = resp.Body.Close()
	}()

	if resp.StatusCode != http.StatusOK {
		s.Require().Failf("Unexpected response status code: %d", fmt.Sprint(resp.StatusCode))
	}

	payload, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		s.Require().NoError(err)
	}

	s.Require().NotEmpty(payload)

	expectedPayload, err := images.Asset("_assets/tests/qr/defaultQR.png")
	require.Equal(s.T(), payload, expectedPayload)
	s.Require().NoError(err)

	//(siddarthkay) un-comment code block below to generate the file in tests folder
	//f, err := os.Create("image.png")
	//if err != nil {
	//	s.Require().NoError(err)
	//
	//}
	//defer f.Close()
	//_, err = f.Write(payload)
	//
	//if err != nil {
	//	s.Require().NoError(err)
	//}
}