// Copyright 2023 Harness, 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 githook

import (
	"context"
	"errors"
	"fmt"

	"github.com/harness/gitness/app/githook"
	"github.com/harness/gitness/git"
	"github.com/harness/gitness/git/hook"
	"github.com/harness/gitness/store"
	"github.com/harness/gitness/types"

	"github.com/rs/zerolog/log"
)

var _ hook.ClientFactory = (*ControllerClientFactory)(nil)
var _ hook.Client = (*ControllerClient)(nil)

// ControllerClientFactory creates clients that directly call the controller to execute githooks.
type ControllerClientFactory struct {
	githookCtrl *Controller
	git         git.Interface
}

func (f *ControllerClientFactory) NewClient(envVars map[string]string) (hook.Client, error) {
	payload, err := hook.LoadPayloadFromMap[githook.Payload](envVars)
	if err != nil {
		return nil, fmt.Errorf("failed to load payload from provided map of environment variables: %w", err)
	}

	// ensure we return disabled message in case it's explicitly disabled
	if payload.Disabled {
		return hook.NewNoopClient([]string{"hook disabled"}), nil
	}

	if err := payload.Validate(); err != nil {
		return nil, fmt.Errorf("payload validation failed: %w", err)
	}

	return &ControllerClient{
		baseInput:   githook.GetInputBaseFromPayload(payload),
		githookCtrl: f.githookCtrl,
		git:         f.git,
	}, nil
}

// ControllerClient directly calls the controller to execute githooks.
type ControllerClient struct {
	baseInput   types.GithookInputBase
	githookCtrl *Controller
	git         RestrictedGIT
}

func (c *ControllerClient) PreReceive(
	ctx context.Context,
	in hook.PreReceiveInput,
) (hook.Output, error) {
	log.Ctx(ctx).Debug().Int64("repo_id", c.baseInput.RepoID).Msg("calling pre-receive")

	out, err := c.githookCtrl.PreReceive(
		ctx,
		c.git, // gitness doesn't require any custom git connector.
		nil,   // TODO: update once githooks are auth protected
		types.GithookPreReceiveInput{
			GithookInputBase: c.baseInput,
			PreReceiveInput:  in,
		},
	)
	if err != nil {
		return hook.Output{}, translateControllerError(err)
	}

	return out, nil
}

func (c *ControllerClient) Update(
	ctx context.Context,
	in hook.UpdateInput,
) (hook.Output, error) {
	log.Ctx(ctx).Debug().Int64("repo_id", c.baseInput.RepoID).Msg("calling update")

	out, err := c.githookCtrl.Update(
		ctx,
		c.git, // gitness doesn't require any custom git connector.
		nil,   // TODO: update once githooks are auth protected
		types.GithookUpdateInput{
			GithookInputBase: c.baseInput,
			UpdateInput:      in,
		},
	)
	if err != nil {
		return hook.Output{}, translateControllerError(err)
	}

	return out, nil
}

func (c *ControllerClient) PostReceive(
	ctx context.Context,
	in hook.PostReceiveInput,
) (hook.Output, error) {
	log.Ctx(ctx).Debug().Int64("repo_id", c.baseInput.RepoID).Msg("calling post-receive")

	out, err := c.githookCtrl.PostReceive(
		ctx,
		c.git, // gitness doesn't require any custom git connector.
		nil,   // TODO: update once githooks are auth protected
		types.GithookPostReceiveInput{
			GithookInputBase: c.baseInput,
			PostReceiveInput: in,
		},
	)
	if err != nil {
		return hook.Output{}, translateControllerError(err)
	}

	return out, nil
}

func translateControllerError(err error) error {
	if errors.Is(err, store.ErrResourceNotFound) {
		return hook.ErrNotFound
	}

	return err
}
