package gitea

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"time"

	"codeberg.org/codeberg/pages/server/cache"
)

const (
	// defaultBranchCacheTimeout specifies the timeout for the default branch cache. It can be quite long.
	defaultBranchCacheTimeout = 15 * time.Minute

	// branchExistenceCacheTimeout specifies the timeout for the branch timestamp & existence cache. It should be shorter
	// than fileCacheTimeout, as that gets invalidated if the branch timestamp has changed. That way, repo changes will be
	// picked up faster, while still allowing the content to be cached longer if nothing changes.
	branchExistenceCacheTimeout = 5 * time.Minute

	// fileCacheTimeout specifies the timeout for the file content cache - you might want to make this quite long, depending
	// on your available memory.
	// TODO: move as option into cache interface
	fileCacheTimeout = 5 * time.Minute

	// fileCacheSizeLimit limits the maximum file size that will be cached, and is set to 1 MB by default.
	fileCacheSizeLimit = int64(1024 * 1024)
)

type FileResponse struct {
	Exists    bool
	IsSymlink bool
	ETag      string
	MimeType  string
	Body      []byte
}

func (f FileResponse) IsEmpty() bool {
	return len(f.Body) != 0
}

func (f FileResponse) createHttpResponse() *http.Response {
	resp := &http.Response{
		Header: make(http.Header),
	}

	if f.Exists {
		resp.StatusCode = http.StatusOK
	} else {
		resp.StatusCode = http.StatusNotFound
	}

	if f.IsSymlink {
		resp.Header.Set(giteaObjectTypeHeader, objTypeSymlink)
	}
	resp.Header.Set(eTagHeader, f.ETag)
	resp.Header.Set(contentTypeHeader, f.MimeType)
	resp.Header.Set(contentLengthHeader, fmt.Sprint(len(f.Body)))
	resp.Header.Set(pagesCacheIndicator, "true")

	return resp
}

type BranchTimestamp struct {
	Branch    string
	Timestamp time.Time
}

type writeCacheReader struct {
	rc       io.ReadCloser
	buff     *bytes.Buffer
	f        *FileResponse
	cacheKey string
	cache    cache.SetGetKey
	hasErr   bool
}

func (t *writeCacheReader) Read(p []byte) (n int, err error) {
	n, err = t.rc.Read(p)
	if err != nil {
		t.hasErr = true
	} else if n > 0 {
		_, _ = t.buff.Write(p[:n])
	}
	return
}

func (t *writeCacheReader) Close() error {
	if !t.hasErr {
		fc := *t.f
		fc.Body = t.buff.Bytes()
		_ = t.cache.Set(t.cacheKey, fc, fileCacheTimeout)
	}
	return t.rc.Close()
}

func (f FileResponse) CreateCacheReader(r io.ReadCloser, cache cache.SetGetKey, cacheKey string) io.ReadCloser {
	buf := []byte{}
	return &writeCacheReader{
		rc:       r,
		buff:     bytes.NewBuffer(buf),
		f:        &f,
		cacheKey: cacheKey,
	}
}