package auth

import (
	"context"
	"fmt"
)

// Stub makes it easier to fake authorization checks in tests.
type Stub struct {
	AllowAllCalls        bool
	CanUseHostingParams  []StubParams
	CanEditChannelParams []StubParams
}

// Stub implements the Auth interface.
var _ Auth = &Stub{}

type StubParams struct {
	CallerID    string
	ChannelID   string
	ShouldError bool
}

func NewAuthStub() *Stub {
	return &Stub{}
}

// NewDenyAllStub returns an Auth stub that always denies authorization checks.
func NewDenyAllStub() *Stub {
	return &Stub{}
}

// NewAllowAllStub returns an Auth stub that always authorizes users.
func NewAllowAllStub() *Stub {
	return &Stub{
		AllowAllCalls: true,
	}
}

// WithHostingPermission updates the given Auth stub, adding permission for the given caller to host/unhost
// the given channel.
func (a *Stub) WithHostingPermission(callerID string, channelID string) *Stub {
	a.CanUseHostingParams = append(a.CanUseHostingParams, StubParams{
		CallerID:  callerID,
		ChannelID: channelID,
	})
	return a
}

// WithCanEditChannel updates the given Auth stub, adding permission for the given caller to edit
// the given channel's settings.
func (a *Stub) WithCanEditChannel(callerID string, channelID string) *Stub {
	a.CanEditChannelParams = append(a.CanEditChannelParams, StubParams{
		CallerID:  callerID,
		ChannelID: channelID,
	})
	return a
}

func (a *Stub) CanUseHosting(ctx context.Context, callerID string, channelID string) (bool, error) {
	return a.canDoOperation(callerID, channelID, a.CanUseHostingParams)
}

func (a *Stub) CanEditChannelSettings(ctx context.Context, callerID string, channelID string) (bool, error) {
	return a.canDoOperation(callerID, channelID, a.CanEditChannelParams)
}

func (a *Stub) canDoOperation(callerID string, channelID string, params []StubParams) (bool, error) {
	if a.AllowAllCalls {
		return true, nil
	}

	for _, p := range params {
		if p.CallerID == callerID && p.ChannelID == channelID {
			if p.ShouldError {
				return false, fmt.Errorf("AuthStub test error")
			}
			return true, nil
		}
	}
	return false, nil
}
