package scm_provider

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"strings"

	bitbucket "github.com/ktrysmt/go-bitbucket"
)

type BitBucketCloudProvider struct {
	client      *ExtendedClient
	allBranches bool
	owner       string
}

type ExtendedClient struct {
	*bitbucket.Client
	username string
	password string
	owner    string
}

func (c *ExtendedClient) GetContents(repo *Repository, path string) (bool, error) {
	urlStr := c.GetApiBaseURL()

	// Getting file contents from V2 defined at https://developer.atlassian.com/cloud/bitbucket/rest/api-group-source/#api-repositories-workspace-repo-slug-src-commit-path-get
	urlStr += fmt.Sprintf("/repositories/%s/%s/src/%s/%s?format=meta", c.owner, repo.Repository, repo.SHA, path)
	body := strings.NewReader("")

	req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, urlStr, body)
	if err != nil {
		return false, err
	}
	req.SetBasicAuth(c.username, c.password)
	resp, err := c.HttpClient.Do(req)
	if err != nil {
		return false, err
	}
	defer resp.Body.Close()
	if resp.StatusCode == http.StatusNotFound {
		return false, nil
	}
	if resp.StatusCode == http.StatusOK {
		return true, nil
	}

	return false, fmt.Errorf("%s", resp.Status)
}

var _ SCMProviderService = &BitBucketCloudProvider{}

func NewBitBucketCloudProvider(owner string, user string, password string, allBranches bool) (*BitBucketCloudProvider, error) {
	bitbucketClient, err := bitbucket.NewBasicAuth(user, password)
	if err != nil {
		return nil, fmt.Errorf("error creating BitBucket Cloud client with basic auth: %w", err)
	}
	client := &ExtendedClient{
		bitbucketClient,
		user,
		password,
		owner,
	}
	return &BitBucketCloudProvider{client: client, owner: owner, allBranches: allBranches}, nil
}

func (g *BitBucketCloudProvider) GetBranches(_ context.Context, repo *Repository) ([]*Repository, error) {
	repos := []*Repository{}
	branches, err := g.listBranches(repo)
	if err != nil {
		return nil, fmt.Errorf("error listing branches for %s/%s: %w", repo.Organization, repo.Repository, err)
	}

	for _, branch := range branches {
		hash, ok := branch.Target["hash"].(string)
		if !ok {
			return nil, fmt.Errorf("error getting SHA for branch for %s/%s/%s: %w", g.owner, repo.Repository, branch.Name, err)
		}
		repos = append(repos, &Repository{
			Organization: repo.Organization,
			Repository:   repo.Repository,
			URL:          repo.URL,
			Branch:       branch.Name,
			SHA:          hash,
			Labels:       repo.Labels,
			RepositoryId: repo.RepositoryId,
		})
	}
	return repos, nil
}

func (g *BitBucketCloudProvider) ListRepos(_ context.Context, cloneProtocol string) ([]*Repository, error) {
	if cloneProtocol == "" {
		cloneProtocol = "ssh"
	}
	opt := &bitbucket.RepositoriesOptions{
		Owner: g.owner,
		Role:  "member",
	}
	repos := []*Repository{}
	accountReposResp, err := g.client.Repositories.ListForAccount(opt)
	if err != nil {
		return nil, fmt.Errorf("error listing repositories for %s: %w", g.owner, err)
	}
	for _, bitBucketRepo := range accountReposResp.Items {
		cloneURL, err := findCloneURL(cloneProtocol, &bitBucketRepo)
		if err != nil {
			return nil, fmt.Errorf("error fetching clone url for repo %s: %w", bitBucketRepo.Slug, err)
		}
		repos = append(repos, &Repository{
			Organization: g.owner,
			Repository:   bitBucketRepo.Slug,
			Branch:       bitBucketRepo.Mainbranch.Name,
			URL:          *cloneURL,
			Labels:       []string{},
			RepositoryId: bitBucketRepo.Uuid,
		})
	}
	return repos, nil
}

func (g *BitBucketCloudProvider) RepoHasPath(_ context.Context, repo *Repository, path string) (bool, error) {
	contents, err := g.client.GetContents(repo, path)
	if err != nil {
		return false, err
	}
	if contents {
		return true, nil
	}
	return false, nil
}

func (g *BitBucketCloudProvider) listBranches(repo *Repository) ([]bitbucket.RepositoryBranch, error) {
	if !g.allBranches {
		repoBranch, err := g.client.Repositories.Repository.GetBranch(&bitbucket.RepositoryBranchOptions{
			Owner:      g.owner,
			RepoSlug:   repo.Repository,
			BranchName: repo.Branch,
		})
		if err != nil {
			return nil, err
		}
		return []bitbucket.RepositoryBranch{
			*repoBranch,
		}, nil
	}

	branches, err := g.client.Repositories.Repository.ListBranches(&bitbucket.RepositoryBranchOptions{
		Owner:    g.owner,
		RepoSlug: repo.Repository,
	})
	if err != nil {
		return nil, err
	}
	return branches.Branches, nil
}

func findCloneURL(cloneProtocol string, repo *bitbucket.Repository) (*string, error) {
	cloneLinks, ok := repo.Links["clone"].([]any)
	if !ok {
		return nil, errors.New("unknown type returned from repo links")
	}
	for _, link := range cloneLinks {
		linkEntry, ok := link.(map[string]any)
		if !ok {
			return nil, errors.New("unknown type returned from clone link")
		}
		if linkEntry["name"] == cloneProtocol {
			url, ok := linkEntry["href"].(string)
			if !ok {
				return nil, errors.New("could not find href for clone link")
			}
			return &url, nil
		}
	}
	return nil, fmt.Errorf("unknown clone protocol for Bitbucket cloud %v", cloneProtocol)
}
