// Copyright 2024 Woodpecker Authors
//
// 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 bitbucketdatacenter

import (
	"fmt"
	"net/url"
	"strings"
	"time"

	"github.com/neticdk/go-bitbucket/bitbucket"
	"golang.org/x/oauth2"

	"go.woodpecker-ci.org/woodpecker/v3/server/model"
)

func convertStatus(status model.StatusValue) bitbucket.BuildStatusState {
	switch status {
	case model.StatusPending, model.StatusRunning:
		return bitbucket.BuildStatusStateInProgress
	case model.StatusSuccess:
		return bitbucket.BuildStatusStateSuccessful
	default:
		return bitbucket.BuildStatusStateFailed
	}
}

func convertID(id uint64) model.ForgeRemoteID {
	return model.ForgeRemoteID(fmt.Sprintf("%d", id))
}

func anonymizeLink(link string) (href string) {
	parsed, err := url.Parse(link)
	if err != nil {
		return link
	}
	parsed.User = nil
	return parsed.String()
}

func convertRepo(from *bitbucket.Repository, perm *model.Perm, branch string) *model.Repo {
	r := &model.Repo{
		ForgeRemoteID: convertID(from.ID),
		Name:          from.Slug,
		Owner:         from.Project.Key,
		Branch:        branch,
		IsSCMPrivate:  true, // Since we have to use Netrc it has to always be private :/ TODO: Is this really true?
		FullName:      fmt.Sprintf("%s/%s", from.Project.Key, from.Slug),
		Perm:          perm,
		PREnabled:     true,
	}

	for _, l := range from.Links["clone"] {
		if l.Name == "http" {
			r.Clone = anonymizeLink(l.Href)
		}
	}

	if l, ok := from.Links["self"]; ok && len(l) > 0 {
		r.ForgeURL = l[0].Href
	}

	return r
}

func convertRepositoryPushEvent(ev *bitbucket.RepositoryPushEvent, baseURL string) *model.Pipeline {
	if len(ev.Changes) == 0 {
		return nil
	}
	change := ev.Changes[0]
	if change.ToHash == "0000000000000000000000000000000000000000" {
		// No ToHash present - could be "DELETE"
		return nil
	}
	if change.Type == bitbucket.RepositoryPushEventChangeTypeDelete {
		return nil
	}

	pipeline := &model.Pipeline{
		Commit:    change.ToHash,
		Branch:    change.Ref.DisplayID,
		Message:   "",
		Avatar:    bitbucketAvatarURL(baseURL, ev.Actor.Slug),
		Author:    authorLabel(ev.Actor.Name),
		Email:     ev.Actor.Email,
		Timestamp: time.Time(ev.Date).UTC().Unix(),
		Ref:       ev.Changes[0].RefId,
		ForgeURL:  fmt.Sprintf("%s/projects/%s/repos/%s/commits/%s", baseURL, ev.Repository.Project.Key, ev.Repository.Slug, change.ToHash),
	}

	if strings.HasPrefix(ev.Changes[0].RefId, "refs/tags/") {
		pipeline.Event = model.EventTag
	} else {
		pipeline.Event = model.EventPush
	}

	return pipeline
}

func convertGetCommitRange(ev *bitbucket.RepositoryPushEvent) (currCommit, prevCommit string) {
	if len(ev.Changes) == 0 {
		return "", ""
	}
	change := ev.Changes[0]
	if change.FromHash == "0000000000000000000000000000000000000000" {
		return change.ToHash, ""
	} else if change.ToHash == "0000000000000000000000000000000000000000" {
		return "", change.FromHash
	}
	return change.ToHash, change.FromHash
}

func convertPullRequestEvent(ev *bitbucket.PullRequestEvent, baseURL string) *model.Pipeline {
	pipeline := &model.Pipeline{
		Commit:    ev.PullRequest.Source.Latest,
		Branch:    ev.PullRequest.Source.DisplayID,
		Title:     ev.PullRequest.Title,
		Message:   ev.PullRequest.Title,
		Avatar:    bitbucketAvatarURL(baseURL, ev.Actor.Slug),
		Author:    authorLabel(ev.Actor.Name),
		Email:     ev.Actor.Email,
		Timestamp: time.Time(ev.Date).UTC().Unix(),
		Ref:       fmt.Sprintf("refs/pull-requests/%d/from", ev.PullRequest.ID),
		ForgeURL:  fmt.Sprintf("%s/projects/%s/repos/%s/commits/%s", baseURL, ev.PullRequest.Source.Repository.Project.Key, ev.PullRequest.Source.Repository.Slug, ev.PullRequest.Source.Latest),
		Refspec:   fmt.Sprintf("%s:%s", ev.PullRequest.Source.DisplayID, ev.PullRequest.Target.DisplayID),
		FromFork:  ev.PullRequest.Source.Repository.ID != ev.PullRequest.Target.Repository.ID,
	}

	if ev.EventKey == bitbucket.EventKeyPullRequestMerged || ev.EventKey == bitbucket.EventKeyPullRequestDeclined || ev.EventKey == bitbucket.EventKeyPullRequestDeleted {
		pipeline.Event = model.EventPullClosed
	} else {
		pipeline.Event = model.EventPull
	}

	return pipeline
}

func authorLabel(name string) string {
	var result string

	const maxNameLength = 40

	if len(name) > maxNameLength {
		result = name[0:37] + "..."
	} else {
		result = name
	}
	return result
}

func convertUser(user *bitbucket.User, baseURL string) *model.User {
	return &model.User{
		ForgeRemoteID: model.ForgeRemoteID(fmt.Sprintf("%d", user.ID)),
		Login:         user.Slug,
		Email:         user.Email,
		Avatar:        bitbucketAvatarURL(baseURL, user.Slug),
	}
}

func bitbucketAvatarURL(baseURL, slug string) string {
	return fmt.Sprintf("%s/users/%s/avatar.png", baseURL, slug)
}

func convertListOptions(p *model.ListOptions) bitbucket.ListOptions {
	if p.All {
		return bitbucket.ListOptions{}
	}
	return bitbucket.ListOptions{Limit: uint(p.PerPage), Start: uint((p.Page - 1) * p.PerPage)}
}

func updateUserCredentials(u *model.User, t *oauth2.Token) {
	u.AccessToken = t.AccessToken
	u.RefreshToken = t.RefreshToken
	u.Expiry = t.Expiry.UTC().Unix()
}

func convertProjectsToTeams(projects []*bitbucket.Project, client *bitbucket.Client) []*model.Team {
	teams := make([]*model.Team, 0)
	for _, project := range projects {
		team := &model.Team{
			Login:  project.Key,
			Avatar: fmt.Sprintf("%s/projects/%s/avatar.png", client.BaseURL, project.Key),
		}
		teams = append(teams, team)
	}
	return teams
}
