package api

import (
	"testing"

	"github.com/grafana/grafana/pkg/api/dtos"
	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/components/simplejson"
	"github.com/grafana/grafana/pkg/middleware"
	m "github.com/grafana/grafana/pkg/models"

	. "github.com/smartystreets/goconvey/convey"
)

func TestDashboardAclApiEndpoint(t *testing.T) {
	Convey("Given a dashboard acl", t, func() {
		mockResult := []*m.DashboardAclInfoDTO{
			{OrgId: 1, DashboardId: 1, UserId: 2, Permission: m.PERMISSION_VIEW},
			{OrgId: 1, DashboardId: 1, UserId: 3, Permission: m.PERMISSION_EDIT},
			{OrgId: 1, DashboardId: 1, UserId: 4, Permission: m.PERMISSION_ADMIN},
			{OrgId: 1, DashboardId: 1, TeamId: 1, Permission: m.PERMISSION_VIEW},
			{OrgId: 1, DashboardId: 1, TeamId: 2, Permission: m.PERMISSION_ADMIN},
		}
		dtoRes := transformDashboardAclsToDTOs(mockResult)

		getDashboardQueryResult := m.NewDashboard("Dash")
		var getDashboardNotFoundError error

		bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
			query.Result = getDashboardQueryResult
			return getDashboardNotFoundError
		})

		bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
			query.Result = dtoRes
			return nil
		})

		bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
			query.Result = mockResult
			return nil
		})

		teamResp := []*m.Team{}
		bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
			query.Result = teamResp
			return nil
		})

		// This tests four scenarios:
		// 1. user is an org admin
		// 2. user is an org editor AND has been granted admin permission for the dashboard
		// 3. user is an org viewer AND has been granted edit permission for the dashboard
		// 4. user is an org editor AND has no permissions for the dashboard

		Convey("When user is org admin", func() {
			loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardsId/acl", m.ROLE_ADMIN, func(sc *scenarioContext) {
				Convey("Should be able to access ACL", func() {
					sc.handlerFunc = GetDashboardAclList
					sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()

					So(sc.resp.Code, ShouldEqual, 200)

					respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
					So(err, ShouldBeNil)
					So(len(respJSON.MustArray()), ShouldEqual, 5)
					So(respJSON.GetIndex(0).Get("userId").MustInt(), ShouldEqual, 2)
					So(respJSON.GetIndex(0).Get("permission").MustInt(), ShouldEqual, m.PERMISSION_VIEW)
				})
			})

			loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_ADMIN, func(sc *scenarioContext) {
				getDashboardNotFoundError = m.ErrDashboardNotFound
				sc.handlerFunc = GetDashboardAclList
				sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()

				Convey("Should not be able to access ACL", func() {
					So(sc.resp.Code, ShouldEqual, 404)
				})
			})

			Convey("Should not be able to update permissions for non-existing dashboard", func() {
				cmd := dtos.UpdateDashboardAclCommand{
					Items: []dtos.DashboardAclUpdateItem{
						{UserId: 1000, Permission: m.PERMISSION_ADMIN},
					},
				}

				postAclScenario("When calling POST on", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_ADMIN, cmd, func(sc *scenarioContext) {
					getDashboardNotFoundError = m.ErrDashboardNotFound
					CallPostAcl(sc)
					So(sc.resp.Code, ShouldEqual, 404)
				})
			})
		})

		Convey("When user is org editor and has admin permission in the ACL", func() {
			loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_EDITOR, func(sc *scenarioContext) {
				mockResult = append(mockResult, &m.DashboardAclInfoDTO{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN})

				Convey("Should be able to access ACL", func() {
					sc.handlerFunc = GetDashboardAclList
					sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()

					So(sc.resp.Code, ShouldEqual, 200)
				})
			})

			Convey("Should not be able to downgrade their own Admin permission", func() {
				cmd := dtos.UpdateDashboardAclCommand{
					Items: []dtos.DashboardAclUpdateItem{
						{UserId: TestUserID, Permission: m.PERMISSION_EDIT},
					},
				}

				postAclScenario("When calling POST on", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_EDITOR, cmd, func(sc *scenarioContext) {
					mockResult = append(mockResult, &m.DashboardAclInfoDTO{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN})

					CallPostAcl(sc)
					So(sc.resp.Code, ShouldEqual, 403)
				})
			})

			Convey("Should be able to update permissions", func() {
				cmd := dtos.UpdateDashboardAclCommand{
					Items: []dtos.DashboardAclUpdateItem{
						{UserId: TestUserID, Permission: m.PERMISSION_ADMIN},
						{UserId: 2, Permission: m.PERMISSION_EDIT},
					},
				}

				postAclScenario("When calling POST on", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_EDITOR, cmd, func(sc *scenarioContext) {
					mockResult = append(mockResult, &m.DashboardAclInfoDTO{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN})

					CallPostAcl(sc)
					So(sc.resp.Code, ShouldEqual, 200)
				})
			})

		})

		Convey("When user is org viewer and has edit permission in the ACL", func() {
			loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_VIEWER, func(sc *scenarioContext) {
				mockResult = append(mockResult, &m.DashboardAclInfoDTO{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_EDIT})

				// Getting the permissions is an Admin permission
				Convey("Should not be able to get list of permissions from ACL", func() {
					sc.handlerFunc = GetDashboardAclList
					sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()

					So(sc.resp.Code, ShouldEqual, 403)
				})
			})
		})

		Convey("When user is org editor and not in the ACL", func() {
			loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardsId/acl", m.ROLE_EDITOR, func(sc *scenarioContext) {

				Convey("Should not be able to access ACL", func() {
					sc.handlerFunc = GetDashboardAclList
					sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()

					So(sc.resp.Code, ShouldEqual, 403)
				})
			})
		})
	})
}

func transformDashboardAclsToDTOs(acls []*m.DashboardAclInfoDTO) []*m.DashboardAclInfoDTO {
	dtos := make([]*m.DashboardAclInfoDTO, 0)

	for _, acl := range acls {
		dto := &m.DashboardAclInfoDTO{
			OrgId:       acl.OrgId,
			DashboardId: acl.DashboardId,
			Permission:  acl.Permission,
			UserId:      acl.UserId,
			TeamId:      acl.TeamId,
		}
		dtos = append(dtos, dto)
	}

	return dtos
}

func CallPostAcl(sc *scenarioContext) {
	bus.AddHandler("test", func(cmd *m.UpdateDashboardAclCommand) error {
		return nil
	})

	sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
}

func postAclScenario(desc string, url string, routePattern string, role m.RoleType, cmd dtos.UpdateDashboardAclCommand, fn scenarioFunc) {
	Convey(desc+" "+url, func() {
		defer bus.ClearBusHandlers()

		sc := setupScenarioContext(url)

		sc.defaultHandler = wrap(func(c *middleware.Context) Response {
			sc.context = c
			sc.context.UserId = TestUserID
			sc.context.OrgId = TestOrgID
			sc.context.OrgRole = role

			return UpdateDashboardAcl(c, cmd)
		})

		sc.m.Post(routePattern, sc.defaultHandler)

		fn(sc)
	})
}
