package api

import (
	"fmt"
	"path"
	"strconv"
	"strings"
	"time"

	"github.com/ncw/rclone/fs"
)

// Error describes a B2 error response
type Error struct {
	Status  int    `json:"status"`  // The numeric HTTP status code. Always matches the status in the HTTP response.
	Code    string `json:"code"`    // A single-identifier code that identifies the error.
	Message string `json:"message"` // A human-readable message, in English, saying what went wrong.
}

// Error statisfies the error interface
func (e *Error) Error() string {
	return fmt.Sprintf("%s (%d %s)", e.Message, e.Status, e.Code)
}

// Fatal statisfies the Fatal interface
//
// It indicates which errors should be treated as fatal
func (e *Error) Fatal() bool {
	return e.Status == 403 // 403 errors shouldn't be retried
}

var _ fs.Fataler = (*Error)(nil)

// Account describes a B2 account
type Account struct {
	ID string `json:"accountId"` // The identifier for the account.
}

// Bucket describes a B2 bucket
type Bucket struct {
	ID        string `json:"bucketId"`
	AccountID string `json:"accountId"`
	Name      string `json:"bucketName"`
	Type      string `json:"bucketType"`
}

// Timestamp is a UTC time when this file was uploaded. It is a base
// 10 number of milliseconds since midnight, January 1, 1970 UTC. This
// fits in a 64 bit integer such as the type "long" in the programming
// language Java. It is intended to be compatible with Java's time
// long. For example, it can be passed directly into the java call
// Date.setTime(long time).
type Timestamp time.Time

// MarshalJSON turns a Timestamp into JSON (in UTC)
func (t *Timestamp) MarshalJSON() (out []byte, err error) {
	timestamp := (*time.Time)(t).UTC().UnixNano()
	return []byte(strconv.FormatInt(timestamp/1E6, 10)), nil
}

// UnmarshalJSON turns JSON into a Timestamp
func (t *Timestamp) UnmarshalJSON(data []byte) error {
	timestamp, err := strconv.ParseInt(string(data), 10, 64)
	if err != nil {
		return err
	}
	*t = Timestamp(time.Unix(timestamp/1E3, (timestamp%1E3)*1E6).UTC())
	return nil
}

const versionFormat = "-v2006-01-02-150405.000"

// AddVersion adds the timestamp as a version string into the filename passed in.
func (t Timestamp) AddVersion(remote string) string {
	ext := path.Ext(remote)
	base := remote[:len(remote)-len(ext)]
	s := (time.Time)(t).Format(versionFormat)
	// Replace the '.' with a '-'
	s = strings.Replace(s, ".", "-", -1)
	return base + s + ext
}

// RemoveVersion removes the timestamp from a filename as a version string.
//
// It returns the new file name and a timestamp, or the old filename
// and a zero timestamp.
func RemoveVersion(remote string) (t Timestamp, newRemote string) {
	newRemote = remote
	ext := path.Ext(remote)
	base := remote[:len(remote)-len(ext)]
	if len(base) < len(versionFormat) {
		return
	}
	versionStart := len(base) - len(versionFormat)
	// Check it ends in -xxx
	if base[len(base)-4] != '-' {
		return
	}
	// Replace with .xxx for parsing
	base = base[:len(base)-4] + "." + base[len(base)-3:]
	newT, err := time.Parse(versionFormat, base[versionStart:])
	if err != nil {
		return
	}
	return Timestamp(newT), base[:versionStart] + ext
}

// IsZero returns true if the timestamp is unitialised
func (t Timestamp) IsZero() bool {
	return (time.Time)(t).IsZero()
}

// Equal compares two timestamps
//
// If either are !IsZero then it returns false
func (t Timestamp) Equal(s Timestamp) bool {
	if (time.Time)(t).IsZero() {
		return false
	}
	if (time.Time)(s).IsZero() {
		return false
	}
	return (time.Time)(t).Equal((time.Time)(s))
}

// File is info about a file
type File struct {
	ID              string            `json:"fileId"`          // The unique identifier for this version of this file. Used with b2_get_file_info, b2_download_file_by_id, and b2_delete_file_version.
	Name            string            `json:"fileName"`        // The name of this file, which can be used with b2_download_file_by_name.
	Action          string            `json:"action"`          // Either "upload" or "hide". "upload" means a file that was uploaded to B2 Cloud Storage. "hide" means a file version marking the file as hidden, so that it will not show up in b2_list_file_names. The result of b2_list_file_names will contain only "upload". The result of b2_list_file_versions may have both.
	Size            int64             `json:"size"`            // The number of bytes in the file.
	UploadTimestamp Timestamp         `json:"uploadTimestamp"` // This is a UTC time when this file was uploaded.
	SHA1            string            `json:"contentSha1"`     // The SHA1 of the bytes stored in the file.
	ContentType     string            `json:"contentType"`     // The MIME type of the file.
	Info            map[string]string `json:"fileInfo"`        // The custom information that was uploaded with the file. This is a JSON object, holding the name/value pairs that were uploaded with the file.
}

// AuthorizeAccountResponse is as returned from the b2_authorize_account call
type AuthorizeAccountResponse struct {
	AccountID          string `json:"accountId"`          // The identifier for the account.
	AuthorizationToken string `json:"authorizationToken"` // An authorization token to use with all calls, other than b2_authorize_account, that need an Authorization header.
	APIURL             string `json:"apiUrl"`             // The base URL to use for all API calls except for uploading and downloading files.
	DownloadURL        string `json:"downloadUrl"`        // The base URL to use for downloading files.
}

// ListBucketsResponse is as returned from the b2_list_buckets call
type ListBucketsResponse struct {
	Buckets []Bucket `json:"buckets"`
}

// ListFileNamesRequest is as passed to b2_list_file_names or b2_list_file_versions
type ListFileNamesRequest struct {
	BucketID      string `json:"bucketId"`                // required - The bucket to look for file names in.
	StartFileName string `json:"startFileName,omitempty"` // optional - The first file name to return. If there is a file with this name, it will be returned in the list. If not, the first file name after this the first one after this name.
	MaxFileCount  int    `json:"maxFileCount,omitempty"`  // optional - The maximum number of files to return from this call. The default value is 100, and the maximum allowed is 1000.
	StartFileID   string `json:"startFileId,omitempty"`   // optional - What to pass in to startFileId for the next search to continue where this one left off.
}

// ListFileNamesResponse is as received from b2_list_file_names or b2_list_file_versions
type ListFileNamesResponse struct {
	Files        []File  `json:"files"`        // An array of objects, each one describing one file.
	NextFileName *string `json:"nextFileName"` // What to pass in to startFileName for the next search to continue where this one left off, or null if there are no more files.
	NextFileID   *string `json:"nextFileId"`   // What to pass in to startFileId for the next search to continue where this one left off, or null if there are no more files.
}

// GetUploadURLRequest is passed to b2_get_upload_url
type GetUploadURLRequest struct {
	BucketID string `json:"bucketId"` // The ID of the bucket that you want to upload to.
}

// GetUploadURLResponse is received from b2_get_upload_url
type GetUploadURLResponse struct {
	BucketID           string `json:"bucketId"`           // The unique ID of the bucket.
	UploadURL          string `json:"uploadUrl"`          // The URL that can be used to upload files to this bucket, see b2_upload_file.
	AuthorizationToken string `json:"authorizationToken"` // The authorizationToken that must be used when uploading files to this bucket, see b2_upload_file.
}

// FileInfo is received from b2_upload_file, b2_get_file_info and b2_finish_large_file
type FileInfo struct {
	ID              string            `json:"fileId"`          // The unique identifier for this version of this file. Used with b2_get_file_info, b2_download_file_by_id, and b2_delete_file_version.
	Name            string            `json:"fileName"`        // The name of this file, which can be used with b2_download_file_by_name.
	Action          string            `json:"action"`          // Either "upload" or "hide". "upload" means a file that was uploaded to B2 Cloud Storage. "hide" means a file version marking the file as hidden, so that it will not show up in b2_list_file_names. The result of b2_list_file_names will contain only "upload". The result of b2_list_file_versions may have both.
	AccountID       string            `json:"accountId"`       // Your account ID.
	BucketID        string            `json:"bucketId"`        // The bucket that the file is in.
	Size            int64             `json:"contentLength"`   // The number of bytes stored in the file.
	UploadTimestamp Timestamp         `json:"uploadTimestamp"` // This is a UTC time when this file was uploaded.
	SHA1            string            `json:"contentSha1"`     // The SHA1 of the bytes stored in the file.
	ContentType     string            `json:"contentType"`     // The MIME type of the file.
	Info            map[string]string `json:"fileInfo"`        // The custom information that was uploaded with the file. This is a JSON object, holding the name/value pairs that were uploaded with the file.
}

// CreateBucketRequest is used to create a bucket
type CreateBucketRequest struct {
	AccountID string `json:"accountId"`
	Name      string `json:"bucketName"`
	Type      string `json:"bucketType"`
}

// DeleteBucketRequest is used to create a bucket
type DeleteBucketRequest struct {
	ID        string `json:"bucketId"`
	AccountID string `json:"accountId"`
}

// DeleteFileRequest is used to delete a file version
type DeleteFileRequest struct {
	ID   string `json:"fileId"`   // The ID of the file, as returned by b2_upload_file, b2_list_file_names, or b2_list_file_versions.
	Name string `json:"fileName"` // The name of this file.
}

// HideFileRequest is used to delete a file
type HideFileRequest struct {
	BucketID string `json:"bucketId"` // The bucket containing the file to hide.
	Name     string `json:"fileName"` // The name of the file to hide.
}

// GetFileInfoRequest is used to return a FileInfo struct with b2_get_file_info
type GetFileInfoRequest struct {
	ID string `json:"fileId"` // The ID of the file, as returned by b2_upload_file, b2_list_file_names, or b2_list_file_versions.
}

// StartLargeFileRequest (b2_start_large_file) Prepares for uploading the parts of a large file.
//
// If the original source of the file being uploaded has a last
// modified time concept, Backblaze recommends using
// src_last_modified_millis as the name, and a string holding the base
// 10 number number of milliseconds since midnight, January 1, 1970
// UTC. This fits in a 64 bit integer such as the type "long" in the
// programming language Java. It is intended to be compatible with
// Java's time long. For example, it can be passed directly into the
// Java call Date.setTime(long time).
//
// If the caller knows the SHA1 of the entire large file being
// uploaded, Backblaze recommends using large_file_sha1 as the name,
// and a 40 byte hex string representing the SHA1.
//
// Example: { "src_last_modified_millis" : "1452802803026", "large_file_sha1" : "a3195dc1e7b46a2ff5da4b3c179175b75671e80d", "color": "blue" }
type StartLargeFileRequest struct {
	BucketID    string            `json:"bucketId"`    //The ID of the bucket that the file will go in.
	Name        string            `json:"fileName"`    // The name of the file. See Files for requirements on file names.
	ContentType string            `json:"contentType"` // The MIME type of the content of the file, which will be returned in the Content-Type header when downloading the file. Use the Content-Type b2/x-auto to automatically set the stored Content-Type post upload. In the case where a file extension is absent or the lookup fails, the Content-Type is set to application/octet-stream.
	Info        map[string]string `json:"fileInfo"`    // A JSON object holding the name/value pairs for the custom file info.
}

// StartLargeFileResponse is the response to StartLargeFileRequest
type StartLargeFileResponse struct {
	ID              string            `json:"fileId"`          // The unique identifier for this version of this file. Used with b2_get_file_info, b2_download_file_by_id, and b2_delete_file_version.
	Name            string            `json:"fileName"`        // The name of this file, which can be used with b2_download_file_by_name.
	AccountID       string            `json:"accountId"`       // The identifier for the account.
	BucketID        string            `json:"bucketId"`        // The unique ID of the bucket.
	ContentType     string            `json:"contentType"`     // The MIME type of the file.
	Info            map[string]string `json:"fileInfo"`        // The custom information that was uploaded with the file. This is a JSON object, holding the name/value pairs that were uploaded with the file.
	UploadTimestamp Timestamp         `json:"uploadTimestamp"` // This is a UTC time when this file was uploaded.
}

// GetUploadPartURLRequest is passed to b2_get_upload_part_url
type GetUploadPartURLRequest struct {
	ID string `json:"fileId"` // The unique identifier of the file being uploaded.
}

// GetUploadPartURLResponse is received from b2_get_upload_url
type GetUploadPartURLResponse struct {
	ID                 string `json:"fileId"`             // The unique identifier of the file being uploaded.
	UploadURL          string `json:"uploadUrl"`          // The URL that can be used to upload files to this bucket, see b2_upload_part.
	AuthorizationToken string `json:"authorizationToken"` // The authorizationToken that must be used when uploading files to this bucket, see b2_upload_part.
}

// UploadPartResponse is the response to b2_upload_part
type UploadPartResponse struct {
	ID         string `json:"fileId"`        // The unique identifier of the file being uploaded.
	PartNumber int64  `json:"partNumber"`    // Which part this is (starting from 1)
	Size       int64  `json:"contentLength"` // The number of bytes stored in the file.
	SHA1       string `json:"contentSha1"`   // The SHA1 of the bytes stored in the file.
}

// FinishLargeFileRequest is passed to b2_finish_large_file
//
// The response is a FileInfo object (with extra AccountID and BucketID fields which we ignore).
//
// Large files do not have a SHA1 checksum. The value will always be "none".
type FinishLargeFileRequest struct {
	ID    string   `json:"fileId"`        // The unique identifier of the file being uploaded.
	SHA1s []string `json:"partSha1Array"` // A JSON array of hex SHA1 checksums of the parts of the large file. This is a double-check that the right parts were uploaded in the right order, and that none were missed. Note that the part numbers start at 1, and the SHA1 of the part 1 is the first string in the array, at index 0.
}

// CancelLargeFileRequest is passed to b2_finish_large_file
//
// The response is a CancelLargeFileResponse
type CancelLargeFileRequest struct {
	ID string `json:"fileId"` // The unique identifier of the file being uploaded.
}

// CancelLargeFileResponse is the response to CancelLargeFileRequest
type CancelLargeFileResponse struct {
	ID        string `json:"fileId"`    // The unique identifier of the file being uploaded.
	Name      string `json:"fileName"`  // The name of this file.
	AccountID string `json:"accountId"` // The identifier for the account.
	BucketID  string `json:"bucketId"`  // The unique ID of the bucket.
}