//
// Copyright 2021, Sune Keller
//
// 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 gitlab

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"time"
)

type (
	// GenericPackagesServiceInterface defines all the API methods for the GenericPackagesService
	GenericPackagesServiceInterface interface {
		FormatPackageURL(pid any, packageName, packageVersion, fileName string) (string, error)
		PublishPackageFile(pid any, packageName, packageVersion, fileName string, content io.Reader, opt *PublishPackageFileOptions, options ...RequestOptionFunc) (*GenericPackagesFile, *Response, error)
		DownloadPackageFile(pid any, packageName, packageVersion, fileName string, options ...RequestOptionFunc) ([]byte, *Response, error)
	}

	// GenericPackagesService handles communication with the packages related
	// methods of the GitLab API.
	//
	// GitLab docs:
	// https://docs.gitlab.com/user/packages/generic_packages/
	GenericPackagesService struct {
		client *Client
	}
)

var _ GenericPackagesServiceInterface = (*GenericPackagesService)(nil)

// GenericPackagesFile represents a GitLab generic package file.
//
// GitLab API docs:
// https://docs.gitlab.com/user/packages/generic_packages/#publish-a-single-file
type GenericPackagesFile struct {
	ID                     int64                  `json:"id"`
	PackageID              int64                  `json:"package_id"`
	CreatedAt              *time.Time             `json:"created_at"`
	UpdatedAt              *time.Time             `json:"updated_at"`
	Size                   int64                  `json:"size"`
	FileStore              int64                  `json:"file_store"`
	FileMD5                string                 `json:"file_md5"`
	FileSHA1               string                 `json:"file_sha1"`
	FileName               string                 `json:"file_name"`
	File                   GenericPackagesFileURL `json:"file"`
	FileSHA256             string                 `json:"file_sha256"`
	VerificationRetryAt    *time.Time             `json:"verification_retry_at"`
	VerifiedAt             *time.Time             `json:"verified_at"`
	VerificationFailure    bool                   `json:"verification_failure"`
	VerificationRetryCount int64                  `json:"verification_retry_count"`
	VerificationChecksum   string                 `json:"verification_checksum"`
	VerificationState      int64                  `json:"verification_state"`
	VerificationStartedAt  *time.Time             `json:"verification_started_at"`
	NewFilePath            string                 `json:"new_file_path"`
}

// GenericPackagesFileURL represents a GitLab generic package file URL.
//
// GitLab API docs:
// https://docs.gitlab.com/user/packages/generic_packages/#publish-a-single-file
type GenericPackagesFileURL struct {
	URL string `json:"url"`
}

// FormatPackageURL returns the GitLab Package Registry URL for the given artifact metadata, without the BaseURL.
// This does not make a GitLab API request, but rather computes it based on their documentation.
func (s *GenericPackagesService) FormatPackageURL(pid any, packageName, packageVersion, fileName string) (string, error) {
	project, err := parseID(pid)
	if err != nil {
		return "", err
	}
	u := fmt.Sprintf(
		"projects/%s/packages/generic/%s/%s/%s",
		PathEscape(project),
		PathEscape(packageName),
		PathEscape(packageVersion),
		PathEscape(fileName),
	)
	return u, nil
}

// PublishPackageFileOptions represents the available PublishPackageFile()
// options.
//
// GitLab docs:
// https://docs.gitlab.com/user/packages/generic_packages/#publish-a-single-file
type PublishPackageFileOptions struct {
	Status *GenericPackageStatusValue `url:"status,omitempty" json:"status,omitempty"`
	Select *GenericPackageSelectValue `url:"select,omitempty" json:"select,omitempty"`
}

// PublishPackageFile uploads a file to a project's package registry.
//
// GitLab docs:
// https://docs.gitlab.com/user/packages/generic_packages/#publish-a-single-file
func (s *GenericPackagesService) PublishPackageFile(pid any, packageName, packageVersion, fileName string, content io.Reader, opt *PublishPackageFileOptions, options ...RequestOptionFunc) (*GenericPackagesFile, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/packages/generic/%s/%s/%s",
		PathEscape(project),
		PathEscape(packageName),
		PathEscape(packageVersion),
		PathEscape(fileName),
	)

	// We need to create the request as a GET request to make sure the options
	// are set correctly. After the request is created we will overwrite both
	// the method and the body.
	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	// Overwrite the method and body.
	req.Method = http.MethodPut
	req.SetBody(content)

	f := new(GenericPackagesFile)
	resp, err := s.client.Do(req, f)
	if err != nil {
		return nil, resp, err
	}

	return f, resp, nil
}

// DownloadPackageFile allows you to download the package file.
//
// GitLab docs:
// https://docs.gitlab.com/user/packages/generic_packages/#download-a-single-file
func (s *GenericPackagesService) DownloadPackageFile(pid any, packageName, packageVersion, fileName string, options ...RequestOptionFunc) ([]byte, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/packages/generic/%s/%s/%s",
		PathEscape(project),
		PathEscape(packageName),
		PathEscape(packageVersion),
		PathEscape(fileName),
	)

	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	var f bytes.Buffer
	resp, err := s.client.Do(req, &f)
	if err != nil {
		return nil, resp, err
	}

	return f.Bytes(), resp, err
}
