package api

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"testing"

	"github.com/grafana/grafana/pkg/api/routing"
	"github.com/grafana/grafana/pkg/components/simplejson"
	"github.com/grafana/grafana/pkg/models"
	acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
	"github.com/grafana/grafana/pkg/services/dashboardimport"
	"github.com/grafana/grafana/pkg/web/webtest"
	"github.com/stretchr/testify/require"
)

func TestImportDashboardAPI(t *testing.T) {
	t.Run("Quota not reached, schema loader service disabled", func(t *testing.T) {
		importDashboardServiceCalled := false
		service := &serviceMock{
			importDashboardFunc: func(ctx context.Context, req *dashboardimport.ImportDashboardRequest) (*dashboardimport.ImportDashboardResponse, error) {
				importDashboardServiceCalled = true
				return nil, nil
			},
		}

		schemaLoaderServiceCalled := false
		schemaLoaderService := &schemaLoaderServiceMock{
			dashboardApplyDefaultsFunc: func(input *simplejson.Json) (*simplejson.Json, error) {
				schemaLoaderServiceCalled = true
				return input, nil
			},
		}

		importDashboardAPI := New(service, quotaServiceFunc(quotaNotReached), schemaLoaderService, nil, acmock.New().WithDisabled())
		routeRegister := routing.NewRouteRegister()
		importDashboardAPI.RegisterAPIEndpoints(routeRegister)
		s := webtest.NewServer(t, routeRegister)

		t.Run("Not signed in should return 404", func(t *testing.T) {
			cmd := &dashboardimport.ImportDashboardRequest{}
			jsonBytes, err := json.Marshal(cmd)
			require.NoError(t, err)
			req := s.NewPostRequest("/api/dashboards/import", bytes.NewReader(jsonBytes))
			resp, err := s.SendJSON(req)
			require.NoError(t, err)
			require.NoError(t, resp.Body.Close())
			require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
		})

		t.Run("Signed in, empty plugin id and dashboard model empty should return error", func(t *testing.T) {
			cmd := &dashboardimport.ImportDashboardRequest{
				PluginId:  "",
				Dashboard: nil,
			}
			jsonBytes, err := json.Marshal(cmd)
			require.NoError(t, err)
			req := s.NewPostRequest("/api/dashboards/import", bytes.NewReader(jsonBytes))
			webtest.RequestWithSignedInUser(req, &models.SignedInUser{
				UserId: 1,
			})
			resp, err := s.SendJSON(req)
			require.NoError(t, err)
			require.NoError(t, resp.Body.Close())
			require.Equal(t, http.StatusUnprocessableEntity, resp.StatusCode)
		})

		t.Run("Signed in, dashboard model set should call import dashboard service", func(t *testing.T) {
			cmd := &dashboardimport.ImportDashboardRequest{
				Dashboard: simplejson.New(),
			}
			jsonBytes, err := json.Marshal(cmd)
			require.NoError(t, err)
			req := s.NewPostRequest("/api/dashboards/import", bytes.NewReader(jsonBytes))
			webtest.RequestWithSignedInUser(req, &models.SignedInUser{
				UserId: 1,
			})
			resp, err := s.SendJSON(req)
			require.NoError(t, err)
			require.NoError(t, resp.Body.Close())
			require.Equal(t, http.StatusOK, resp.StatusCode)
			require.True(t, importDashboardServiceCalled)
		})

		t.Run("Signed in, dashboard model set, trimdefaults enabled should not call schema loader service", func(t *testing.T) {
			cmd := &dashboardimport.ImportDashboardRequest{
				Dashboard: simplejson.New(),
			}
			jsonBytes, err := json.Marshal(cmd)
			require.NoError(t, err)
			req := s.NewPostRequest("/api/dashboards/import?trimdefaults=true", bytes.NewReader(jsonBytes))
			webtest.RequestWithSignedInUser(req, &models.SignedInUser{
				UserId: 1,
			})
			resp, err := s.SendJSON(req)
			require.NoError(t, err)
			require.NoError(t, resp.Body.Close())
			require.Equal(t, http.StatusOK, resp.StatusCode)
			require.False(t, schemaLoaderServiceCalled)
			require.True(t, importDashboardServiceCalled)
		})
	})

	t.Run("Quota not reached, schema loader service enabled", func(t *testing.T) {
		importDashboardServiceCalled := false
		service := &serviceMock{
			importDashboardFunc: func(ctx context.Context, req *dashboardimport.ImportDashboardRequest) (*dashboardimport.ImportDashboardResponse, error) {
				importDashboardServiceCalled = true
				return nil, nil
			},
		}

		schemaLoaderServiceCalled := false
		schemaLoaderService := &schemaLoaderServiceMock{
			enabled: true,
			dashboardApplyDefaultsFunc: func(input *simplejson.Json) (*simplejson.Json, error) {
				schemaLoaderServiceCalled = true
				return input, nil
			},
		}

		importDashboardAPI := New(service, quotaServiceFunc(quotaNotReached), schemaLoaderService, nil, acmock.New().WithDisabled())
		routeRegister := routing.NewRouteRegister()
		importDashboardAPI.RegisterAPIEndpoints(routeRegister)
		s := webtest.NewServer(t, routeRegister)

		t.Run("Signed in, dashboard model set, trimdefaults enabled should call schema loader service", func(t *testing.T) {
			cmd := &dashboardimport.ImportDashboardRequest{
				Dashboard: simplejson.New(),
			}
			jsonBytes, err := json.Marshal(cmd)
			require.NoError(t, err)
			req := s.NewPostRequest("/api/dashboards/import?trimdefaults=true", bytes.NewReader(jsonBytes))
			webtest.RequestWithSignedInUser(req, &models.SignedInUser{
				UserId: 1,
			})
			resp, err := s.SendJSON(req)
			require.NoError(t, err)
			require.NoError(t, resp.Body.Close())
			require.Equal(t, http.StatusOK, resp.StatusCode)
			require.True(t, schemaLoaderServiceCalled)
			require.True(t, importDashboardServiceCalled)
		})
	})

	t.Run("Quota reached", func(t *testing.T) {
		service := &serviceMock{}
		schemaLoaderService := &schemaLoaderServiceMock{}
		importDashboardAPI := New(service, quotaServiceFunc(quotaReached), schemaLoaderService, nil, acmock.New().WithDisabled())

		routeRegister := routing.NewRouteRegister()
		importDashboardAPI.RegisterAPIEndpoints(routeRegister)
		s := webtest.NewServer(t, routeRegister)

		t.Run("Signed in, dashboard model set, should return 403 forbidden/quota reached", func(t *testing.T) {
			cmd := &dashboardimport.ImportDashboardRequest{
				Dashboard: simplejson.New(),
			}
			jsonBytes, err := json.Marshal(cmd)
			require.NoError(t, err)
			req := s.NewPostRequest("/api/dashboards/import", bytes.NewReader(jsonBytes))
			webtest.RequestWithSignedInUser(req, &models.SignedInUser{
				UserId: 1,
			})
			resp, err := s.SendJSON(req)
			require.NoError(t, err)
			require.NoError(t, resp.Body.Close())
			require.Equal(t, http.StatusForbidden, resp.StatusCode)
		})
	})
}

type serviceMock struct {
	importDashboardFunc func(ctx context.Context, req *dashboardimport.ImportDashboardRequest) (*dashboardimport.ImportDashboardResponse, error)
}

func (s *serviceMock) ImportDashboard(ctx context.Context, req *dashboardimport.ImportDashboardRequest) (*dashboardimport.ImportDashboardResponse, error) {
	if s.importDashboardFunc != nil {
		return s.importDashboardFunc(ctx, req)
	}

	return nil, nil
}

type schemaLoaderServiceMock struct {
	enabled                    bool
	dashboardApplyDefaultsFunc func(input *simplejson.Json) (*simplejson.Json, error)
}

func (s *schemaLoaderServiceMock) IsDisabled() bool {
	return !s.enabled
}

func (s *schemaLoaderServiceMock) DashboardApplyDefaults(input *simplejson.Json) (*simplejson.Json, error) {
	if s.dashboardApplyDefaultsFunc != nil {
		return s.dashboardApplyDefaultsFunc(input)
	}

	return input, nil
}

func quotaReached(c *models.ReqContext, target string) (bool, error) {
	return true, nil
}

func quotaNotReached(c *models.ReqContext, target string) (bool, error) {
	return false, nil
}
