package storage

import (
	"context"
	"fmt"
	"log/slog"
	"time"

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

// GCSStore implements Store using Google Cloud Storage.
type GCSStore struct {
	client *gcsstorage.Client
	bucket string
	logger *slog.Logger
}

// NewGCSStore creates a GCS-backed store.
// credentialsJSON may be empty to use Application Default Credentials.
func NewGCSStore(ctx context.Context, bucket string, credentialsJSON string, logger *slog.Logger) (*GCSStore, error) {
	var opts []option.ClientOption
	if credentialsJSON != "" {
		opts = append(opts, option.WithCredentialsJSON([]byte(credentialsJSON)))
	}

	client, err := gcsstorage.NewClient(ctx, opts...)
	if err != nil {
		return nil, fmt.Errorf("storage: failed to create GCS client: %w", err)
	}

	return &GCSStore{
		client: client,
		bucket: bucket,
		logger: logger,
	}, nil
}

func (s *GCSStore) Upload(ctx context.Context, path string, data []byte, contentType string) (string, error) {
	obj := s.client.Bucket(s.bucket).Object(path)
	w := obj.NewWriter(ctx)
	w.ContentType = contentType

	if _, err := w.Write(data); err != nil {
		_ = w.Close()
		return "", fmt.Errorf("storage: write failed: %w", err)
	}
	if err := w.Close(); err != nil {
		return "", fmt.Errorf("storage: close failed: %w", err)
	}

	// Return a signed URL consistent with GetURL (bucket is private).
	return s.GetURL(ctx, path)
}

func (s *GCSStore) UploadPresigned(ctx context.Context, path string, contentType string) (*PresignedUpload, error) {
	expires := time.Now().Add(15 * time.Minute)
	url, err := s.client.Bucket(s.bucket).SignedURL(path, &gcsstorage.SignedURLOptions{
		Method:      "PUT",
		ContentType: contentType,
		Expires:     expires,
	})
	if err != nil {
		return nil, fmt.Errorf("storage: presigned URL failed: %w", err)
	}

	return &PresignedUpload{
		URL:     url,
		Headers: map[string]string{"Content-Type": contentType},
		Method:  "PUT",
		Expires: expires,
	}, nil
}

func (s *GCSStore) GetURL(ctx context.Context, path string) (string, error) {
	url, err := s.client.Bucket(s.bucket).SignedURL(path, &gcsstorage.SignedURLOptions{
		Method:  "GET",
		Expires: time.Now().Add(1 * time.Hour),
	})
	if err != nil {
		return "", fmt.Errorf("storage: signed URL failed: %w", err)
	}
	return url, nil
}

func (s *GCSStore) Delete(ctx context.Context, path string) error {
	if err := s.client.Bucket(s.bucket).Object(path).Delete(ctx); err != nil {
		return fmt.Errorf("storage: delete failed: %w", err)
	}
	return nil
}

func (s *GCSStore) List(ctx context.Context, prefix string) ([]MediaObject, error) {
	it := s.client.Bucket(s.bucket).Objects(ctx, &gcsstorage.Query{Prefix: prefix})
	var objects []MediaObject
	for {
		attrs, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return nil, fmt.Errorf("storage: list failed: %w", err)
		}
		signedURL, urlErr := s.GetURL(ctx, attrs.Name)
		if urlErr != nil {
			s.logger.Warn("failed to sign URL for listed object", "path", attrs.Name, "error", urlErr)
			continue
		}
		objects = append(objects, MediaObject{
			Path:        attrs.Name,
			URL:         signedURL,
			ContentType: attrs.ContentType,
			Size:        attrs.Size,
			CreatedAt:   attrs.Created,
		})
	}
	return objects, nil
}

// Close releases GCS client resources.
func (s *GCSStore) Close() error {
	return s.client.Close()
}
