package api

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/grafana/grafana/pkg/plugins"
	"github.com/grafana/grafana/pkg/services/org"
	"github.com/grafana/grafana/pkg/services/plugindashboards"
	"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
	"github.com/grafana/grafana/pkg/services/quota/quotatest"
	"github.com/grafana/grafana/pkg/services/user"
	"github.com/grafana/grafana/pkg/web/webtest"
)

func TestGetPluginDashboards(t *testing.T) {
	const existingPluginID = "existing-plugin"
	pluginDashboardService := &pluginDashboardServiceMock{
		pluginDashboards: map[string][]*plugindashboards.PluginDashboard{
			existingPluginID: {
				{
					PluginId: existingPluginID,
					UID:      "a",
					Title:    "A",
				},
				{
					PluginId: existingPluginID,
					UID:      "b",
					Title:    "B",
				},
			},
		},
		unexpectedErrors: map[string]error{
			"boom": errors.New("BOOM"),
		},
	}

	s := SetupAPITestServer(t, func(hs *HTTPServer) {
		hs.pluginDashboardService = pluginDashboardService
		hs.QuotaService = quotatest.New(false, nil)
		hs.pluginStore = &pluginstore.FakePluginStore{
			PluginList: []pluginstore.Plugin{
				{JSONData: plugins.JSONData{ID: existingPluginID}},
				{JSONData: plugins.JSONData{ID: "boom"}},
			},
		}
	})

	t.Run("Not signed in should return 404 Not Found", func(t *testing.T) {
		req := s.NewGetRequest("/api/plugins/test/dashboards")
		resp, err := s.Send(req)
		require.NoError(t, err)
		require.NoError(t, resp.Body.Close())
		require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
	})

	t.Run("Signed in and not org admin should return 403 Forbidden", func(t *testing.T) {
		user := &user.SignedInUser{
			UserID:  1,
			OrgRole: org.RoleEditor,
		}

		resp, err := sendGetPluginDashboardsRequestForSignedInUser(t, s, existingPluginID, user)
		require.NoError(t, err)
		require.NoError(t, resp.Body.Close())
		require.Equal(t, http.StatusForbidden, resp.StatusCode)
	})

	t.Run("Signed in and org admin", func(t *testing.T) {
		user := &user.SignedInUser{
			UserID:  1,
			OrgID:   1,
			OrgRole: org.RoleAdmin,
		}

		t.Run("When plugin doesn't exist should return 404 Not Found", func(t *testing.T) {
			resp, err := sendGetPluginDashboardsRequestForSignedInUser(t, s, "not-exists", user)
			require.NoError(t, err)
			require.NoError(t, resp.Body.Close())
			require.Equal(t, http.StatusNotFound, resp.StatusCode)
		})

		t.Run("When result is unexpected error should return 500 Internal Server Error", func(t *testing.T) {
			resp, err := sendGetPluginDashboardsRequestForSignedInUser(t, s, "boom", user)
			require.NoError(t, err)
			require.NoError(t, resp.Body.Close())
			require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
		})

		t.Run("When plugin exists should return 200 OK with expected payload", func(t *testing.T) {
			resp, err := sendGetPluginDashboardsRequestForSignedInUser(t, s, existingPluginID, user)
			require.NoError(t, err)
			require.Equal(t, http.StatusOK, resp.StatusCode)
			bytes, err := io.ReadAll(resp.Body)
			require.NoError(t, err)
			require.NoError(t, resp.Body.Close())
			var listResp []*plugindashboards.PluginDashboard
			err = json.Unmarshal(bytes, &listResp)
			require.NoError(t, err)
			require.NotNil(t, listResp)
			require.Len(t, listResp, 2)
			require.Equal(t, pluginDashboardService.pluginDashboards[existingPluginID], listResp)
		})
	})
}

func sendGetPluginDashboardsRequestForSignedInUser(t *testing.T, s *webtest.Server, pluginID string, user *user.SignedInUser) (*http.Response, error) {
	t.Helper()

	req := s.NewGetRequest(fmt.Sprintf("/api/plugins/%s/dashboards", pluginID))
	webtest.RequestWithSignedInUser(req, user)
	return s.Send(req)
}

type pluginDashboardServiceMock struct {
	plugindashboards.Service
	pluginDashboards map[string][]*plugindashboards.PluginDashboard
	unexpectedErrors map[string]error
}

func (m *pluginDashboardServiceMock) ListPluginDashboards(ctx context.Context, req *plugindashboards.ListPluginDashboardsRequest) (*plugindashboards.ListPluginDashboardsResponse, error) {
	if pluginDashboards, exists := m.pluginDashboards[req.PluginID]; exists {
		return &plugindashboards.ListPluginDashboardsResponse{
			Items: pluginDashboards,
		}, nil
	}

	if err, exists := m.unexpectedErrors[req.PluginID]; exists {
		return nil, err
	}

	return nil, plugins.NotFoundError{PluginID: req.PluginID}
}
