package lib

import (
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"os"
	"path/filepath"
)

var (
	ErrObjectNotFound = errors.New("object not found")
)

// ObjectStore is the interface for encrypted file storage.
type ObjectStore interface {
	Write(projectID string, data []byte) (string, error)
	Read(projectID, objectID string) ([]byte, error)
	Delete(objectID string) error
	Exists(objectID string) bool
}

// LocalStore implements ObjectStore using the local filesystem.
// Files are stored in a two-level directory structure based on the first 4 hex chars of the ID.
type LocalStore struct {
	BasePath  string
	MasterKey []byte
}

// NewLocalStore creates a new local filesystem object store.
func NewLocalStore(basePath string) (*LocalStore, error) {
	if err := os.MkdirAll(basePath, 0700); err != nil {
		return nil, err
	}
	return &LocalStore{BasePath: basePath}, nil
}

func (s *LocalStore) objectPath(id string) string {
	// Two-level sharding: ab/cd/abcdef...
	if len(id) < 4 {
		return filepath.Join(s.BasePath, id)
	}
	return filepath.Join(s.BasePath, id[:2], id[2:4], id)
}

// Write encrypts data and writes to store. Returns the object ID.
func (s *LocalStore) Write(projectID string, data []byte) (string, error) {
	// Derive project-specific key if master key is set
	if len(s.MasterKey) > 0 {
		key, err := DeriveProjectKey(s.MasterKey, projectID)
		if err != nil {
			return "", err
		}
		encrypted, err := ObjectEncrypt(key, data)
		if err != nil {
			return "", err
		}
		data = encrypted
	}

	// Compute content-addressable ID
	id := ObjectID(data)
	path := s.objectPath(id)
	if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
		return "", err
	}
	if err := os.WriteFile(path, data, 0600); err != nil {
		return "", err
	}
	return id, nil
}

// Read reads and decrypts data from store.
func (s *LocalStore) Read(projectID, objectID string) ([]byte, error) {
	data, err := os.ReadFile(s.objectPath(objectID))
	if errors.Is(err, os.ErrNotExist) {
		return nil, ErrObjectNotFound
	}
	if err != nil {
		return nil, err
	}

	// Decrypt if master key is set
	if len(s.MasterKey) > 0 {
		key, err := DeriveProjectKey(s.MasterKey, projectID)
		if err != nil {
			return nil, err
		}
		data, err = ObjectDecrypt(key, data)
		if err != nil {
			return nil, err
		}
	}

	return data, nil
}

func (s *LocalStore) Delete(objectID string) error {
	err := os.Remove(s.objectPath(objectID))
	if errors.Is(err, os.ErrNotExist) {
		return nil
	}
	return err
}

func (s *LocalStore) Exists(objectID string) bool {
	_, err := os.Stat(s.objectPath(objectID))
	return err == nil
}

// ObjectID computes the content-addressable ID (hex SHA-256 of encrypted content).
func ObjectID(encryptedData []byte) string {
	h := sha256.Sum256(encryptedData)
	return hex.EncodeToString(h[:])
}

// ObjectWrite encrypts data and writes to store. Returns the object ID.
func ObjectWrite(db *DB, store ObjectStore, cfg *Config, projectID string, data []byte) (string, error) {
	return store.Write(projectID, data)
}

// ObjectRead reads and decrypts data from store.
func ObjectRead(db *DB, store ObjectStore, cfg *Config, projectID, objectID string) ([]byte, error) {
	return store.Read(projectID, objectID)
}

// ObjectDelete removes an object from store.
func ObjectDelete(store ObjectStore, objectID string) error {
	return store.Delete(objectID)
}
