package store

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"os"
	"path/filepath"
)

type Record struct {
	Hash string
	Path string
	Size int64
}

type ContentStore struct {
	root string
}

func New(root string) (*ContentStore, error) {
	if err := os.MkdirAll(root, 0o755); err != nil {
		return nil, fmt.Errorf("create content store root: %w", err)
	}

	return &ContentStore{root: root}, nil
}

func (s *ContentStore) PutBytes(content []byte) (Record, error) {
	return s.put(bytes.NewReader(content))
}

func (s *ContentStore) PutReader(reader io.Reader) (Record, error) {
	return s.put(reader)
}

func (s *ContentStore) Read(hash string) ([]byte, error) {
	return os.ReadFile(s.PathForHash(hash))
}

func (s *ContentStore) PathForHash(hash string) string {
	return filepath.Join(s.root, hash[0:2], hash[2:4], hash)
}

func (s *ContentStore) put(reader io.Reader) (Record, error) {
	content, err := io.ReadAll(reader)
	if err != nil {
		return Record{}, fmt.Errorf("read content: %w", err)
	}

	sum := sha256.Sum256(content)
	hash := hex.EncodeToString(sum[:])
	target := s.PathForHash(hash)

	if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
		return Record{}, fmt.Errorf("create content directory: %w", err)
	}

	if _, err := os.Stat(target); err == nil {
		return Record{Hash: hash, Path: target, Size: int64(len(content))}, nil
	}

	if err := os.WriteFile(target, content, 0o644); err != nil {
		return Record{}, fmt.Errorf("write content file: %w", err)
	}

	return Record{Hash: hash, Path: target, Size: int64(len(content))}, nil
}
