package services

import (
	"encoding/json"
	"net/http"
	"net/url"

	"github.com/jfrog/jfrog-client-go/auth"
	"github.com/jfrog/jfrog-client-go/http/jfroghttpclient"
	"github.com/jfrog/jfrog-client-go/utils/errorutils"
	"github.com/jfrog/jfrog-client-go/utils/log"
)

const apiRepositories = "api/repositories"

type RepositoriesService struct {
	client     *jfroghttpclient.JfrogHttpClient
	ArtDetails auth.ServiceDetails
}

func NewRepositoriesService(client *jfroghttpclient.JfrogHttpClient) *RepositoriesService {
	return &RepositoriesService{client: client}
}

// Get fetches repository details from Artifactory using given repoKey (name) into the given params struct.
// The function expects to get the repo key and a pointer to a param struct that will be filled up.
// The param struct should contain the desired param's fields corresponded to the Artifactory REST API, such as RepositoryDetails, LocalRepositoryBaseParams, etc.
func (rs *RepositoriesService) Get(repoKey string, repoDetails interface{}) error {
	log.Debug("Getting repository '" + repoKey + "' details ...")
	body, err := rs.sendGet(apiRepositories + "/" + repoKey)
	if err != nil {
		return err
	}
	err = json.Unmarshal(body, repoDetails)
	return errorutils.CheckError(err)
}

func (rs *RepositoriesService) IsExists(repoKey string) (exists bool, err error) {
	httpClientsDetails := rs.ArtDetails.CreateHttpClientDetails()
	resp, _, _, err := rs.client.SendGet(rs.ArtDetails.GetUrl()+apiRepositories+"/"+repoKey, true, &httpClientsDetails)
	if err != nil {
		return false, errorutils.CheckError(err)
	}
	return resp.StatusCode == http.StatusOK, nil
}

func (rs *RepositoriesService) GetAll() (*[]RepositoryDetails, error) {
	log.Info("Getting all repositories ...")
	return rs.GetWithFilter(RepositoriesFilterParams{})
}

func (rs *RepositoriesService) GetWithFilter(params RepositoriesFilterParams) (*[]RepositoryDetails, error) {
	body, err := rs.sendGet(rs.createUrlWithFilter(params))
	if err != nil {
		return nil, err
	}
	repoDetails := &[]RepositoryDetails{}
	err = json.Unmarshal(body, &repoDetails)
	return repoDetails, errorutils.CheckError(err)
}

// This function is used to create the URL for the repositories API with the given filter params.
// The function expects to get a RepositoriesFilterParams struct that contains the desired filter params.
// The function returns the URL string.
func (rs *RepositoriesService) createUrlWithFilter(params RepositoriesFilterParams) string {
	u := url.URL{
		Path: apiRepositories,
	}

	queryParams := url.Values{}
	if params.RepoType != "" {
		queryParams.Add("type", params.RepoType)
	}
	if params.PackageType != "" {
		queryParams.Add("packageType", params.PackageType)
	}
	if params.ProjectKey != "" {
		queryParams.Add("project", params.ProjectKey)
	}

	u.RawQuery = queryParams.Encode()
	return u.String()
}

func (rs *RepositoriesService) sendGet(api string) ([]byte, error) {
	httpClientsDetails := rs.ArtDetails.CreateHttpClientDetails()
	resp, body, _, err := rs.client.SendGet(rs.ArtDetails.GetUrl()+api, true, &httpClientsDetails)
	if err != nil {
		return nil, err
	}
	if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK); err != nil {
		return nil, err
	}
	log.Debug("Artifactory response:", resp.Status)
	log.Debug("Done getting repository details.")
	return body, nil
}

func (rs *RepositoriesService) Create(params interface{}, repoName string) error {
	repositoryService := &RepositoryService{
		ArtDetails: rs.ArtDetails,
		client:     rs.client,
		isUpdate:   false,
	}
	return repositoryService.performRequest(params, repoName)
}

func (rs *RepositoriesService) Update(params interface{}, repoName string) error {
	repositoryService := &RepositoryService{
		ArtDetails: rs.ArtDetails,
		client:     rs.client,
		isUpdate:   true,
	}
	return repositoryService.performRequest(params, repoName)
}

type RepositoryDetails struct {
	Key         string
	Rclass      string
	Type        string
	Description string
	Url         string
	PackageType string
}

func (rd RepositoryDetails) GetRepoType() string {
	// When getting All repos from artifactory the REST returns with Type field,
	// but when getting a specific repo it will return with the Rclass field.
	if rd.Rclass != "" {
		return rd.Rclass
	}
	return rd.Type
}

type RepositoriesFilterParams struct {
	RepoType    string
	PackageType string
	ProjectKey  string
}

func NewRepositoriesFilterParams() RepositoriesFilterParams {
	return RepositoriesFilterParams{}
}
