// Copyright (C) The Arvados Authors. All rights reserved.
//
// SPDX-License-Identifier: AGPL-3.0

package localdb

import (
	"git.arvados.org/arvados.git/lib/ctrlctx"
	"git.arvados.org/arvados.git/sdk/go/arvados"
	"git.arvados.org/arvados.git/sdk/go/arvadostest"
	check "gopkg.in/check.v1"
)

var _ = check.Suite(&GroupSuite{})

type GroupSuite struct {
	localdbSuite
}

func (s *GroupSuite) TestGroupCreateWithProperties(c *check.C) {
	s.setUpVocabulary(c, "")

	tests := []struct {
		name    string
		props   map[string]interface{}
		success bool
	}{
		{"Invalid prop key", map[string]interface{}{"Priority": "IDVALIMPORTANCES1"}, false},
		{"Invalid prop value", map[string]interface{}{"IDTAGIMPORTANCES": "high"}, false},
		{"Valid prop key & value", map[string]interface{}{"IDTAGIMPORTANCES": "IDVALIMPORTANCES1"}, true},
		{"Empty properties", map[string]interface{}{}, true},
	}
	for _, tt := range tests {
		c.Log(c.TestName()+" ", tt.name)

		grp, err := s.localdb.GroupCreate(s.userctx, arvados.CreateOptions{
			Select: []string{"uuid", "properties"},
			Attrs: map[string]interface{}{
				"group_class": "project",
				"properties":  tt.props,
			}})
		if tt.success {
			c.Assert(err, check.IsNil)
			c.Assert(grp.Properties, check.DeepEquals, tt.props)
		} else {
			c.Assert(err, check.NotNil)
		}
	}
}

func (s *GroupSuite) TestGroupUpdateWithProperties(c *check.C) {
	s.setUpVocabulary(c, "")

	tests := []struct {
		name    string
		props   map[string]interface{}
		success bool
	}{
		{"Invalid prop key", map[string]interface{}{"Priority": "IDVALIMPORTANCES1"}, false},
		{"Invalid prop value", map[string]interface{}{"IDTAGIMPORTANCES": "high"}, false},
		{"Valid prop key & value", map[string]interface{}{"IDTAGIMPORTANCES": "IDVALIMPORTANCES1"}, true},
		{"Empty properties", map[string]interface{}{}, true},
	}
	for _, tt := range tests {
		c.Log(c.TestName()+" ", tt.name)
		grp, err := s.localdb.GroupCreate(s.userctx, arvados.CreateOptions{
			Attrs: map[string]interface{}{
				"group_class": "project",
			},
		})
		c.Assert(err, check.IsNil)
		grp, err = s.localdb.GroupUpdate(s.userctx, arvados.UpdateOptions{
			UUID:   grp.UUID,
			Select: []string{"uuid", "properties"},
			Attrs: map[string]interface{}{
				"properties": tt.props,
			}})
		if tt.success {
			c.Assert(err, check.IsNil)
			c.Assert(grp.Properties, check.DeepEquals, tt.props)
		} else {
			c.Assert(err, check.NotNil)
		}
	}
}

func (s *GroupSuite) TestCanWriteCanManageResponses(c *check.C) {
	ctxUser1 := ctrlctx.NewWithToken(s.ctx, s.cluster, arvadostest.ActiveTokenV2)
	ctxUser2 := ctrlctx.NewWithToken(s.ctx, s.cluster, arvadostest.SpectatorToken)
	ctxAdmin := ctrlctx.NewWithToken(s.ctx, s.cluster, arvadostest.AdminToken)
	project, err := s.localdb.GroupCreate(ctxUser1, arvados.CreateOptions{
		Attrs: map[string]interface{}{
			"group_class": "project",
		},
	})
	c.Assert(err, check.IsNil)
	c.Check(project.CanWrite, check.Equals, true)
	c.Check(project.CanManage, check.Equals, true)

	subproject, err := s.localdb.GroupCreate(ctxUser1, arvados.CreateOptions{
		Attrs: map[string]interface{}{
			"owner_uuid":  project.UUID,
			"group_class": "project",
		},
	})
	c.Assert(err, check.IsNil)
	c.Check(subproject.CanWrite, check.Equals, true)
	c.Check(subproject.CanManage, check.Equals, true)

	projlist, err := s.localdb.GroupList(ctxUser1, arvados.ListOptions{
		Limit:   -1,
		Filters: []arvados.Filter{{"uuid", "in", []string{project.UUID, subproject.UUID}}},
	})
	c.Assert(err, check.IsNil)
	c.Assert(projlist.Items, check.HasLen, 2)
	for _, p := range projlist.Items {
		c.Check(p.CanWrite, check.Equals, true)
		c.Check(p.CanManage, check.Equals, true)
	}

	// Give 2nd user permission to read
	permlink, err := s.localdb.LinkCreate(ctxAdmin, arvados.CreateOptions{
		Attrs: map[string]interface{}{
			"link_class": "permission",
			"name":       "can_read",
			"tail_uuid":  arvadostest.SpectatorUserUUID,
			"head_uuid":  project.UUID,
		},
	})
	c.Assert(err, check.IsNil)

	// As 2nd user: can read, cannot manage, cannot write
	project2, err := s.localdb.GroupGet(ctxUser2, arvados.GetOptions{UUID: project.UUID})
	c.Assert(err, check.IsNil)
	c.Check(project2.CanWrite, check.Equals, false)
	c.Check(project2.CanManage, check.Equals, false)

	_, err = s.localdb.LinkUpdate(ctxAdmin, arvados.UpdateOptions{
		UUID: permlink.UUID,
		Attrs: map[string]interface{}{
			"name": "can_write",
		},
	})
	c.Assert(err, check.IsNil)

	// As 2nd user: cannot manage, can write
	project2, err = s.localdb.GroupGet(ctxUser2, arvados.GetOptions{UUID: project.UUID})
	c.Assert(err, check.IsNil)
	c.Check(project2.CanWrite, check.Equals, true)
	c.Check(project2.CanManage, check.Equals, false)

	// As owner: after freezing, can manage (owner), cannot write (frozen)
	project, err = s.localdb.GroupUpdate(ctxUser1, arvados.UpdateOptions{
		UUID: project.UUID,
		Attrs: map[string]interface{}{
			"frozen_by_uuid": arvadostest.ActiveUserUUID,
		}})
	c.Assert(err, check.IsNil)
	c.Check(project.CanWrite, check.Equals, false)
	c.Check(project.CanManage, check.Equals, true)

	// As admin: can manage (admin), cannot write (frozen)
	project, err = s.localdb.GroupGet(ctxAdmin, arvados.GetOptions{UUID: project.UUID})
	c.Assert(err, check.IsNil)
	c.Check(project.CanWrite, check.Equals, false)
	c.Check(project.CanManage, check.Equals, true)

	// As 2nd user: cannot manage (perm), cannot write (frozen)
	project2, err = s.localdb.GroupGet(ctxUser2, arvados.GetOptions{UUID: project.UUID})
	c.Assert(err, check.IsNil)
	c.Check(project2.CanWrite, check.Equals, false)
	c.Check(project2.CanManage, check.Equals, false)

	// After upgrading perm to "manage", as 2nd user: can manage (perm), cannot write (frozen)
	_, err = s.localdb.LinkUpdate(ctxAdmin, arvados.UpdateOptions{
		UUID: permlink.UUID,
		Attrs: map[string]interface{}{
			"name": "can_manage",
		},
	})
	c.Assert(err, check.IsNil)
	project2, err = s.localdb.GroupGet(ctxUser2, arvados.GetOptions{UUID: project.UUID})
	c.Assert(err, check.IsNil)
	c.Check(project2.CanWrite, check.Equals, false)
	c.Check(project2.CanManage, check.Equals, true)

	// 2nd user can also manage (but not write) the subject inside the frozen project
	subproject2, err := s.localdb.GroupGet(ctxUser2, arvados.GetOptions{UUID: subproject.UUID})
	c.Assert(err, check.IsNil)
	c.Check(subproject2.CanWrite, check.Equals, false)
	c.Check(subproject2.CanManage, check.Equals, true)

	u, err := s.localdb.UserGet(ctxUser1, arvados.GetOptions{
		UUID: arvadostest.ActiveUserUUID,
	})
	c.Assert(err, check.IsNil)
	c.Check(u.CanWrite, check.Equals, true)
	c.Check(u.CanManage, check.Equals, true)

	for _, selectParam := range [][]string{
		nil,
		{"can_write", "can_manage"},
	} {
		c.Logf("selectParam: %+v", selectParam)
		ulist, err := s.localdb.UserList(ctxUser1, arvados.ListOptions{
			Limit:   -1,
			Filters: []arvados.Filter{{"uuid", "=", arvadostest.ActiveUserUUID}},
			Select:  selectParam,
		})
		c.Assert(err, check.IsNil)
		c.Assert(ulist.Items, check.HasLen, 1)
		c.Logf("%+v", ulist.Items)
		for _, u := range ulist.Items {
			c.Check(u.CanWrite, check.Equals, true)
			c.Check(u.CanManage, check.Equals, true)
		}
	}
}