package securityevents

import (
	"context"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
)

var secretReferencePattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,127}$`)

var ErrSecretNotFound = errors.New("security event secret not found")

type SecretStore interface {
	Put(context.Context, string, []byte) error
	Get(context.Context, string) ([]byte, error)
	Delete(context.Context, string) error
}

type FileSecretStore struct{ directory string }

func NewFileSecretStore(directory string) (*FileSecretStore, error) {
	if directory == "" {
		return nil, errors.New("security event secret directory is required")
	}
	if err := os.MkdirAll(directory, 0o700); err != nil {
		return nil, fmt.Errorf("create security event secret directory: %w", err)
	}
	if err := os.Chmod(directory, 0o700); err != nil {
		return nil, fmt.Errorf("protect security event secret directory: %w", err)
	}
	return &FileSecretStore{directory: directory}, nil
}

func (s *FileSecretStore) Put(_ context.Context, reference string, value []byte) error {
	path, err := s.path(reference)
	if err != nil {
		return err
	}
	if len(value) < 32 || len(value) > 4096 {
		return errors.New("security event secret length is invalid")
	}
	temporary, err := os.CreateTemp(s.directory, ".ssf-secret-*")
	if err != nil {
		return fmt.Errorf("create temporary security event secret: %w", err)
	}
	temporaryName := temporary.Name()
	defer func() { _ = os.Remove(temporaryName) }()
	if err := temporary.Chmod(0o600); err != nil {
		_ = temporary.Close()
		return err
	}
	if _, err := temporary.Write(value); err != nil {
		_ = temporary.Close()
		return fmt.Errorf("write security event secret: %w", err)
	}
	if err := temporary.Sync(); err != nil {
		_ = temporary.Close()
		return err
	}
	if err := temporary.Close(); err != nil {
		return err
	}
	if err := os.Rename(temporaryName, path); err != nil {
		return fmt.Errorf("publish security event secret: %w", err)
	}
	return os.Chmod(path, 0o600)
}

func (s *FileSecretStore) Get(_ context.Context, reference string) ([]byte, error) {
	path, err := s.path(reference)
	if err != nil {
		return nil, err
	}
	info, err := os.Stat(path)
	if errors.Is(err, os.ErrNotExist) {
		return nil, ErrSecretNotFound
	}
	if err != nil || !info.Mode().IsRegular() || info.Mode().Perm()&0o077 != 0 {
		return nil, errors.New("security event secret file is not protected")
	}
	value, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read security event secret: %w", err)
	}
	if len(value) < 32 || len(value) > 4096 {
		clear(value)
		return nil, errors.New("security event secret length is invalid")
	}
	return value, nil
}

func (s *FileSecretStore) Delete(_ context.Context, reference string) error {
	path, err := s.path(reference)
	if err != nil {
		return err
	}
	err = os.Remove(path)
	if errors.Is(err, os.ErrNotExist) {
		return nil
	}
	return err
}

func (s *FileSecretStore) path(reference string) (string, error) {
	if !secretReferencePattern.MatchString(reference) {
		return "", errors.New("security event secret reference is invalid")
	}
	return filepath.Join(s.directory, reference), nil
}
