// Copyright 2020 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package transfer

import (
	"context"
	"runtime/debug"

	"github.com/drone/drone/core"

	"github.com/hashicorp/go-multierror"
	"github.com/sirupsen/logrus"
)

// Transferer handles transfering repository ownership from one
// user to another user account.
type Transferer struct {
	Repos core.RepositoryStore
	Perms core.PermStore
}

// New returns a new repository transfer service.
func New(repos core.RepositoryStore, perms core.PermStore) core.Transferer {
	return &Transferer{
		Repos: repos,
		Perms: perms,
	}
}

// Transfer transfers all repositories owned by the specified user
// to an alternate account with sufficient admin permissions.
func (t *Transferer) Transfer(ctx context.Context, user *core.User) error {
	defer func() {
		// taking the paranoid approach to recover from
		// a panic that should absolutely never happen.
		if r := recover(); r != nil {
			logrus.Errorf("transferer: unexpected panic: %s", r)
			debug.PrintStack()
		}
	}()

	repos, err := t.Repos.List(ctx, user.ID)
	if err != nil {
		return err
	}

	var result error
	for _, repo := range repos {
		// only transfer repository ownership if the deactivated
		// user owns the repository.
		if repo.UserID != user.ID {
			continue
		}

		members, err := t.Perms.List(ctx, repo.UID)
		if err != nil {
			result = multierror.Append(result, err)
			continue
		}

		var admin int64
		for _, member := range members {
			// only transfer the repository to an admin user
			// that is not equal to the deactivated user.
			if repo.UserID == member.UserID {
				continue
			}
			if member.Admin {
				admin = member.UserID
				break
			}
		}

		if admin == 0 {
			logrus.
				WithField("repo.id", repo.ID).
				WithField("repo.namespace", repo.Namespace).
				WithField("repo.name", repo.Name).
				Traceln("repository disabled")
		} else {
			logrus.
				WithField("repo.id", repo.ID).
				WithField("repo.namespace", repo.Namespace).
				WithField("repo.name", repo.Name).
				WithField("old.user.id", repo.UserID).
				WithField("new.user.id", admin).
				Traceln("repository owner re-assigned")
		}

		// if no alternate user was found the repository id
		// is reset to the zero value, indicating the repository
		// has no owner.
		repo.UserID = admin
		err = t.Repos.Update(ctx, repo)
		if err != nil {
			result = multierror.Append(result, err)
		}
	}

	return result
}
