package gcp

import (
	"context"
	"fmt"
	"io"
	"os"
	"path"
	"sync"

	"cloud.google.com/go/storage"
	"google.golang.org/api/iterator"
)

var csonce sync.Once

// Clients should be reused instead of created as needed. The methods of Client are safe for concurrent use by multiple goroutines.
// Since the client will be reclaimed by the system at the end of the program's lifecycle, we do not have to call the cleanup process
var cscli *storage.Client

// GcpProviderGcs provide the access to GCP GCS
type GcpProviderGcs struct {
	cli     *storage.Client
	project string
	bucket  string
}

func NewGcpProviderGcs(project string, bucket string) *GcpProviderGcs {
	csonce.Do(func() {
		cli, err := storage.NewClient(context.Background())
		if err != nil {
			//we will panic at first use if any error found
			panic(fmt.Sprintf("create cloud storage client failed in gcp vendor err: %s", err))
		}
		cscli = cli
	})
	return &GcpProviderGcs{
		cli:     cscli,
		project: project,
		bucket:  bucket,
	}
}

// ListObjectInTime not support in GCP
func (g *GcpProviderGcs) ListObjectInTime(ctx context.Context, now int64, perfix string) ([]string, error) {
	panic("for gcs we not implement this function")
}

// ListObjectByName lists  blobs which giving name  in the bucket
func (g *GcpProviderGcs) ListObjectByName(ctx context.Context, perfix string) ([]string, error) {
	var result []string
	it := g.cli.Bucket(g.bucket).Objects(ctx, &storage.Query{Prefix: perfix})
	for {
		attrs, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return nil, err
		}
		result = append(result, attrs.Name)
	}
	return result, nil
}

func (g *GcpProviderGcs) DownloadObjects(ctx context.Context, objects []string, destinationDir string) error {
	for _, name := range objects {
		rc, err := g.cli.Bucket(g.bucket).Object(name).NewReader(ctx)
		if err != nil {
			return err
		}
		defer rc.Close()
		// create file
		filePath := path.Join(destinationDir, path.Base(name))
		file, err := os.Create(filePath)
		if err != nil {
			return fmt.Errorf("failed to create file %s, %v", filePath, err)
		}
		defer file.Close()
		// write to file
		_, err = file.ReadFrom(rc)
		if err != nil {
			return fmt.Errorf("failed to write to file %s, %v", filePath, err)
		}
	}
	return nil
}

func (g *GcpProviderGcs) UploadObject(ctx context.Context, objectName string, sourcefile string) error {
	file, err := os.Open(sourcefile)
	if err != nil {
		return fmt.Errorf("failed to open file %s, %v", sourcefile, err)
	}
	defer file.Close()
	wc := g.cli.Bucket(g.bucket).Object(objectName).NewWriter(ctx)
	if _, err = io.Copy(wc, file); err != nil {
		return fmt.Errorf("failed to write to file %s, %v", sourcefile, err)
	}
	if err := wc.Close(); err != nil {
		return fmt.Errorf("failed to close writer %v", err)
	}
	return nil
}
