// SPDX-FileCopyrightText: 2021 Luke Granger-Brown <depot@lukegb.com>
//
// SPDX-License-Identifier: Apache-2.0

package fuphttp

import (
	"context"
	"fmt"
	"io"
	"log"
	"net/http"
	"time"

	"github.com/gorilla/mux"
	"gocloud.dev/blob"
	"gocloud.dev/gcerrors"
)

func (a *Application) rawDownload(rw http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	vars := mux.Vars(r)
	filename := vars["filename"]

	meta, err := metadata(ctx, a.storageBackend, filename)
	switch errorCode(err) {
	case gcerrors.NotFound:
		a.notFound(rw, r)
		return
	default:
		log.Printf("rawDownload(%q) metadata: %v", filename, err)
		a.internalError(rw, r)
		return
	case gcerrors.OK:
		// OK
	}

	// Do things with attributes?
	if a.redirectToBlobstore {
		redirectExpiry := a.redirectExpiry
		if !meta.ExpiresAt.IsZero() {
			// Cap the redirect lifetime to the remaining lifetime of the object.
			remainingLifetime := meta.ExpiresAt.Sub(time.Now())
			if remainingLifetime < redirectExpiry {
				redirectExpiry = remainingLifetime
			}
		}
		u, err := a.storageBackend.SignedURL(ctx, filename, &blob.SignedURLOptions{
			Expiry: redirectExpiry,
		})
		switch errorCode(err) {
		case gcerrors.NotFound:
			// This is unlikely to get returned.
			a.notFound(rw, r)
			return
		case gcerrors.OK:
			http.Redirect(rw, r, u, http.StatusFound)
			return
		case gcerrors.Unimplemented:
			// Fall back to serving directly.
		default:
			log.Printf("rawDownload(%q) SignedURL (continuing with fallback): %v", filename, err)
		}
	}

	// Perform direct serving.
	a.rawDownloadDirect(ctx, rw, r, filename, meta)
}

func (a *Application) rawDownloadDirect(ctx context.Context, rw http.ResponseWriter, r *http.Request, filename string, meta *Metadata) {
	// TODO(lukegb): Range header and conditionals?.
	rdr, err := a.storageBackend.NewReader(ctx, filename, nil)
	switch errorCode(err) {
	case gcerrors.NotFound:
		a.notFound(rw, r)
		return
	default:
		a.internalError(rw, r)
		return
	case gcerrors.OK:
		// OK
	}
	defer rdr.Close()

	attrs := meta.Attributes
	if v := attrs.CacheControl; v != "" {
		rw.Header().Set("Cache-Control", v)
	}
	if v := attrs.ContentDisposition; v != "" {
		rw.Header().Set("Content-Disposition", v)
	}
	if v := attrs.ContentEncoding; v != "" {
		rw.Header().Set("Content-Encoding", v)
	}
	if v := attrs.ContentLanguage; v != "" {
		rw.Header().Set("Content-Language", v)
	}
	if v := attrs.ContentType; v != "" {
		rw.Header().Set("Content-Type", v)
	}
	if v := attrs.ETag; v != "" {
		rw.Header().Set("ETag", v)
	}

	// If we're serving from local disk, use http.ServeContent, using this somewhat opaque method.
	var ior io.Reader
	if rdr.As(&ior) {
		if iors, ok := ior.(io.ReadSeeker); ok {
			http.ServeContent(rw, r, filename, attrs.ModTime, iors)
			return
		}
	}

	if v := attrs.ModTime; !v.IsZero() {
		rw.Header().Set("Last-Modified", v.UTC().Format(http.TimeFormat))
	}
	if v := attrs.Size; v != 0 {
		rw.Header().Set("Content-Length", fmt.Sprintf("%d", v))
	}

	if _, err := io.Copy(rw, rdr); err != nil {
		log.Printf("rawDownloadDirect(%q) copy: %v", filename, err)
	}
}