//
// Copyright 2021, Sander van Harmelen
//
// 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 (
	"math/big"
	"net/http"
	"time"
)

type (
	TagsServiceInterface interface {
		// ListTags gets a list of tags from a project, sorted by name in reverse
		// alphabetical order.
		//
		// GitLab API docs:
		// https://docs.gitlab.com/api/tags/#list-project-repository-tags
		ListTags(pid any, opt *ListTagsOptions, options ...RequestOptionFunc) ([]*Tag, *Response, error)
		// GetTag a specific repository tag determined by its name. It returns 200 together
		// with the tag information if the tag exists. It returns 404 if the tag does not exist.
		//
		// GitLab API docs:
		// https://docs.gitlab.com/api/tags/#get-a-single-repository-tag
		GetTag(pid any, tag string, options ...RequestOptionFunc) (*Tag, *Response, error)
		// GetTagSignature a specific repository tag determined by its name. It returns 200 together
		// with the signature if the tag exists. It returns 404 if the tag does not exist.
		//
		// GitLab API docs:
		// https://docs.gitlab.com/api/tags/#get-x509-signature-of-a-tag
		GetTagSignature(pid any, tag string, options ...RequestOptionFunc) (*X509Signature, *Response, error)
		// CreateTag creates a new tag in the repository that points to the supplied ref.
		//
		// GitLab API docs:
		// https://docs.gitlab.com/api/tags/#create-a-new-tag
		CreateTag(pid any, opt *CreateTagOptions, options ...RequestOptionFunc) (*Tag, *Response, error)
		// DeleteTag deletes a tag of a repository with given name.
		//
		// GitLab API docs:
		// https://docs.gitlab.com/api/tags/#delete-a-tag
		DeleteTag(pid any, tag string, options ...RequestOptionFunc) (*Response, error)
	}

	// TagsService handles communication with the tags related methods
	// of the GitLab API.
	//
	// GitLab API docs: https://docs.gitlab.com/api/tags/
	TagsService struct {
		client *Client
	}
)

var _ TagsServiceInterface = (*TagsService)(nil)

// Tag represents a GitLab tag.
//
// GitLab API docs: https://docs.gitlab.com/api/tags/
type Tag struct {
	Commit    *Commit      `json:"commit"`
	Release   *ReleaseNote `json:"release"`
	Name      string       `json:"name"`
	Message   string       `json:"message"`
	Protected bool         `json:"protected"`
	Target    string       `json:"target"`
	CreatedAt *time.Time   `json:"created_at"`
}

// X509Signature represents a GitLab Tag Signature object.
//
// GitLab API docs: https://docs.gitlab.com/api/tags/#get-x509-signature-of-a-tag
type X509Signature struct {
	SignatureType      string          `json:"signature_type"`
	VerificationStatus string          `json:"verification_status"`
	X509Certificate    X509Certificate `json:"x509_certificate"`
}

type X509Certificate struct {
	ID                   int64      `json:"id"`
	Subject              string     `json:"subject"`
	SubjectKeyIdentifier string     `json:"subject_key_identifier"`
	Email                string     `json:"email"`
	SerialNumber         *big.Int   `json:"serial_number"`
	CertificateStatus    string     `json:"certificate_status"`
	X509Issuer           X509Issuer `json:"x509_issuer"`
}

type X509Issuer struct {
	ID                   int64  `json:"id"`
	Subject              string `json:"subject"`
	SubjectKeyIdentifier string `json:"subject_key_identifier"`
	CrlURL               string `json:"crl_url"`
}

// ReleaseNote represents a GitLab version release.
//
// GitLab API docs: https://docs.gitlab.com/api/tags/
type ReleaseNote struct {
	TagName     string `json:"tag_name"`
	Description string `json:"description"`
}

func (t Tag) String() string {
	return Stringify(t)
}

// ListTagsOptions represents the available ListTags() options.
//
// GitLab API docs:
// https://docs.gitlab.com/api/tags/#list-project-repository-tags
type ListTagsOptions struct {
	ListOptions
	OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"`
	Search  *string `url:"search,omitempty" json:"search,omitempty"`
	Sort    *string `url:"sort,omitempty" json:"sort,omitempty"`
}

func (s *TagsService) ListTags(pid any, opt *ListTagsOptions, options ...RequestOptionFunc) ([]*Tag, *Response, error) {
	return do[[]*Tag](s.client,
		withPath("projects/%s/repository/tags", ProjectID{pid}),
		withAPIOpts(opt),
		withRequestOpts(options...),
	)
}

func (s *TagsService) GetTag(pid any, tag string, options ...RequestOptionFunc) (*Tag, *Response, error) {
	return do[*Tag](s.client,
		withPath("projects/%s/repository/tags/%s", ProjectID{pid}, tag),
		withRequestOpts(options...),
	)
}

func (s *TagsService) GetTagSignature(pid any, tag string, options ...RequestOptionFunc) (*X509Signature, *Response, error) {
	return do[*X509Signature](s.client,
		withPath("projects/%s/repository/tags/%s/signature", ProjectID{pid}, tag),
		withRequestOpts(options...),
	)
}

// CreateTagOptions represents the available CreateTag() options.
//
// GitLab API docs:
// https://docs.gitlab.com/api/tags/#create-a-new-tag
type CreateTagOptions struct {
	TagName *string `url:"tag_name,omitempty" json:"tag_name,omitempty"`
	Ref     *string `url:"ref,omitempty" json:"ref,omitempty"`
	Message *string `url:"message,omitempty" json:"message,omitempty"`
}

func (s *TagsService) CreateTag(pid any, opt *CreateTagOptions, options ...RequestOptionFunc) (*Tag, *Response, error) {
	return do[*Tag](s.client,
		withMethod(http.MethodPost),
		withPath("projects/%s/repository/tags", ProjectID{pid}),
		withAPIOpts(opt),
		withRequestOpts(options...),
	)
}

func (s *TagsService) DeleteTag(pid any, tag string, options ...RequestOptionFunc) (*Response, error) {
	_, resp, err := do[none](s.client,
		withMethod(http.MethodDelete),
		withPath("projects/%s/repository/tags/%s", ProjectID{pid}, tag),
		withRequestOpts(options...),
	)
	return resp, err
}
