// 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 notification

import (
	"bytes"
	"context"
	"fmt"

	pullreqevents "github.com/harness/gitness/app/events/pullreq"
	"github.com/harness/gitness/app/services/notification/mailer"
	"github.com/harness/gitness/types"
)

const (
	TemplateReviewerAdded        = "reviewer_added.html"
	TemplateCommentPRAuthor      = "comment_pr_author.html"
	TemplateCommentMentions      = "comment_mentions.html"
	TemplateCommentParticipants  = "comment_participants.html"
	TemplatePullReqBranchUpdated = "pullreq_branch_updated.html"
	TemplateNameReviewSubmitted  = "review_submitted.html"
	TemplatePullReqStateChanged  = "pullreq_state_changed.html"
)

type MailClient struct {
	mailer.Mailer
}

func NewMailClient(mailer mailer.Mailer) MailClient {
	return MailClient{
		Mailer: mailer,
	}
}

func (m MailClient) SendCommentPRAuthor(
	ctx context.Context,
	recipients []*types.PrincipalInfo,
	payload *CommentPayload,
) error {
	email, err := GenerateEmailFromPayload(
		TemplateCommentPRAuthor,
		recipients,
		payload.Base,
		payload,
	)
	if err != nil {
		return fmt.Errorf("failed to generate mail requests after processing %s event: %w",
			pullreqevents.CommentCreatedEvent, err)
	}

	return m.Mailer.Send(ctx, *email)
}
func (m MailClient) SendCommentMentions(
	ctx context.Context,
	recipients []*types.PrincipalInfo,
	payload *CommentPayload,
) error {
	email, err := GenerateEmailFromPayload(
		TemplateCommentMentions,
		recipients,
		payload.Base,
		payload,
	)
	if err != nil {
		return fmt.Errorf("failed to generate mail requests after processing %s event: %w",
			pullreqevents.CommentCreatedEvent, err)
	}

	return m.Mailer.Send(ctx, *email)
}
func (m MailClient) SendCommentParticipants(
	ctx context.Context,
	recipients []*types.PrincipalInfo,
	payload *CommentPayload,
) error {
	email, err := GenerateEmailFromPayload(
		TemplateCommentParticipants,
		recipients,
		payload.Base,
		payload,
	)
	if err != nil {
		return fmt.Errorf("failed to generate mail requests after processing %s event: %w",
			pullreqevents.CommentCreatedEvent, err)
	}

	return m.Mailer.Send(ctx, *email)
}

func (m MailClient) SendReviewerAdded(
	ctx context.Context,
	recipients []*types.PrincipalInfo,
	payload *ReviewerAddedPayload,
) error {
	email, err := GenerateEmailFromPayload(
		TemplateReviewerAdded,
		recipients,
		payload.Base,
		payload,
	)
	if err != nil {
		return fmt.Errorf("failed to generate mail requests after processing %s event: %w",
			pullreqevents.ReviewerAddedEvent, err)
	}

	return m.Mailer.Send(ctx, *email)
}

func (m MailClient) SendPullReqBranchUpdated(
	ctx context.Context,
	recipients []*types.PrincipalInfo,
	payload *PullReqBranchUpdatedPayload,
) error {
	email, err := GenerateEmailFromPayload(
		TemplatePullReqBranchUpdated,
		recipients,
		payload.Base,
		payload,
	)
	if err != nil {
		return fmt.Errorf("failed to generate mail requests after processing %s event: %w",
			pullreqevents.BranchUpdatedEvent, err)
	}

	return m.Mailer.Send(ctx, *email)
}

func (m MailClient) SendReviewSubmitted(
	ctx context.Context,
	recipients []*types.PrincipalInfo,
	payload *ReviewSubmittedPayload,
) error {
	email, err := GenerateEmailFromPayload(TemplateNameReviewSubmitted, recipients, payload.Base, payload)
	if err != nil {
		return fmt.Errorf(
			"failed to generate mail requests after processing %s event: %w",
			pullreqevents.ReviewSubmittedEvent,
			err,
		)
	}
	return m.Mailer.Send(ctx, *email)
}

func (m MailClient) SendPullReqStateChanged(
	ctx context.Context,
	recipients []*types.PrincipalInfo,
	payload *PullReqStateChangedPayload,
) error {
	email, err := GenerateEmailFromPayload(TemplatePullReqStateChanged, recipients, payload.Base, payload)
	if err != nil {
		return fmt.Errorf(
			"failed to generate mail requests after processing pullReqState change event: %w",
			err,
		)
	}

	return m.Mailer.Send(ctx, *email)
}

func GetSubjectPullRequest(
	repoIdentifier string,
	prNum int64,
	prTitle string,
) string {
	return fmt.Sprintf(subjectPullReqEvent, repoIdentifier, prTitle, prNum)
}

func GetHTMLBody(templateName string, data interface{}) ([]byte, error) {
	tmpl := htmlTemplates[templateName]
	tmplOutput := bytes.Buffer{}
	err := tmpl.Execute(&tmplOutput, data)
	if err != nil {
		return nil, fmt.Errorf("failed to execute template %s", templateName)
	}

	return tmplOutput.Bytes(), nil
}

func GenerateEmailFromPayload(
	templateName string,
	recipients []*types.PrincipalInfo,
	base *BasePullReqPayload,
	payload interface{},
) (*mailer.Payload, error) {
	subject := GetSubjectPullRequest(base.Repo.Identifier, base.PullReq.Number,
		base.PullReq.Title)

	body, err := GetHTMLBody(templateName, payload)
	if err != nil {
		return nil, err
	}

	var email mailer.Payload
	email.Body = string(body)
	email.Subject = subject
	email.RepoRef = base.Repo.Path

	recipientEmails := RetrieveEmailsFromPrincipals(recipients)
	email.ToRecipients = recipientEmails
	return &email, nil
}

func RetrieveEmailsFromPrincipals(principals []*types.PrincipalInfo) []string {
	emails := make([]string, len(principals))
	for i, principal := range principals {
		emails[i] = principal.Email
	}
	return emails
}
