package filesystem

import (
	"context"
	"encoding/json"
	"log/slog"
	"strings"
	"time"

	"github.com/valkey-io/valkey-go"
)

const keyRead = "webdav:read:"

type readFile struct {
	Age     time.Time `json:"age"`
	Content []byte    `json:"content"`
}

func (c *CachedClient) Read(path string) ([]byte, error) {
	key := keyRead + path
	ctx := context.Background()

	// Try cache
	cached, err := c.cache.Do(ctx, c.cache.B().Get().Key(key).Build()).AsBytes()
	if err == nil && len(cached) > 0 {
		c.readHits.Add(1)
		c.hits.Add(1)

		var file readFile
		err := json.Unmarshal(cached, &file)
		if err != nil {
			slog.Error("could not Unmarshal cache", "key", key, "err", err)
		} else {
			return file.Content, nil
		}

	}

	// Cache miss
	c.misses.Add(1)
	slog.Debug("cache miss", "key", key)

	data, err := c.impl.Read(path)
	if err != nil {
		return nil, err
	}
	file := readFile{
		Age:     time.Now(),
		Content: data,
	}

	cached, err = json.Marshal(file)
	if err != nil {
		slog.Error("could not marshal file for cache", "key", key, "err", err)
		return data, nil
	}

	// Cache
	c.cache.Do(ctx, c.cache.B().Set().Key(key).Value(valkey.BinaryString(cached)).Ex(c.ttl).Build())

	return data, nil
}

func (c *CachedClient) revalidateRead(ctx context.Context) {
	startTime := time.Now()
	c.scanAndProcess(ctx, keyRead+"*", func(key string) error {
		path := strings.TrimPrefix(key, keyRead)
		cached, err := c.cache.Do(ctx, c.cache.B().Get().Key(key).Build()).AsBytes()
		if err != nil {
			// Key might be gone or error fetching
			return nil
		}

		var cachedFile readFile
		err = json.Unmarshal(cached, &cachedFile)
		if err != nil {
			c.cache.Do(ctx, c.cache.B().Del().Key(key).Build())
			return err
		}

		info, err := c.impl.Stat(path)
		if err != nil {
			c.cache.Do(ctx, c.cache.B().Del().Key(key).Build())
			return nil
		}

		if info.ModTime().After(cachedFile.Age) {
			data, err := c.impl.Read(path)
			if err != nil {
				return err
			}
			cachedFile.Age = time.Now()
			cachedFile.Content = data

			newCached, err := json.Marshal(cachedFile)
			if err != nil {
				return err
			}

			c.cache.Do(ctx, c.cache.B().Set().Key(key).Value(valkey.BinaryString(newCached)).Ex(c.ttl).Build())
		} else {
			c.cache.Do(ctx, c.cache.B().Expire().Key(key).Seconds(int64(c.ttl/time.Second)).Build())
		}
		return nil
	})
	slog.Debug("revalidation of read complete", "duration", time.Since(startTime))
}
