package gcs

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"

	"gitlab.com/dynaweb/ains-microservices/pkg/config"
)

type Store interface {
	Upload(ctx context.Context, object string, r io.Reader, contentType string) error
	Open(ctx context.Context, object string) (io.ReadCloser, error)
	Delete(ctx context.Context, object string) error
	DownloadURL(ctx context.Context, object string, expires time.Duration) (string, error)
}

type httpStore struct {
	endpoint string
	bucket   string
	client   *http.Client
}

func New(endpoint, bucket string) Store {
	return &httpStore{
		endpoint: strings.TrimRight(endpoint, "/"),
		bucket:   bucket,
		client:   &http.Client{Timeout: 30 * time.Second},
	}
}

func NewFromConfig(cfg *config.Config) Store {
	return New(cfg.GCS.Endpoint, cfg.GCS.Bucket)
}

func (s *httpStore) Upload(ctx context.Context, object string, r io.Reader, contentType string) error {
	endpoint := fmt.Sprintf("%s/upload/storage/v1/b/%s/o?uploadType=media&name=%s",
		s.endpoint, url.PathEscape(s.bucket), url.QueryEscape(object))

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, r)
	if err != nil {
		return err
	}
	if contentType != "" {
		req.Header.Set("Content-Type", contentType)
	}

	resp, err := s.client.Do(req)
	if err != nil {
		return err
	}
	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode >= 300 {
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 2048))
		return fmt.Errorf("gcs: upload %s: status %d: %s", object, resp.StatusCode, strings.TrimSpace(string(body)))
	}
	return nil
}

func (s *httpStore) Open(ctx context.Context, object string) (io.ReadCloser, error) {
	resp, err := s.client.Do(s.mediaRequest(ctx, http.MethodGet, object))
	if err != nil {
		return nil, err
	}
	if resp.StatusCode >= 300 {
		_ = resp.Body.Close()
		return nil, fmt.Errorf("gcs: open %s: status %d", object, resp.StatusCode)
	}
	return resp.Body, nil
}

func (s *httpStore) Delete(ctx context.Context, object string) error {
	req, err := http.NewRequestWithContext(ctx, http.MethodDelete, s.objectURL(object), nil)
	if err != nil {
		return err
	}
	resp, err := s.client.Do(req)
	if err != nil {
		return err
	}
	defer func() { _ = resp.Body.Close() }()
	if resp.StatusCode >= 300 && resp.StatusCode != http.StatusNotFound {
		return fmt.Errorf("gcs: delete %s: status %d", object, resp.StatusCode)
	}
	return nil
}

func (s *httpStore) DownloadURL(_ context.Context, object string, _ time.Duration) (string, error) {
	return s.objectURL(object) + "?alt=media", nil
}

func (s *httpStore) mediaRequest(ctx context.Context, method, object string) *http.Request {
	req, _ := http.NewRequestWithContext(ctx, method, s.objectURL(object)+"?alt=media", nil)
	return req
}

func (s *httpStore) objectURL(object string) string {
	return fmt.Sprintf("%s/storage/v1/b/%s/o/%s", s.endpoint, url.PathEscape(s.bucket), url.PathEscape(object))
}
