package store

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"sync"

	"git.casaderoll.de/michael/urbm/internal/model"
)

type Store struct {
	dir string
	mu  sync.RWMutex
}

func New(dir string) *Store { return &Store{dir: dir} }

func (s *Store) Init() error {
	if err := os.MkdirAll(s.dir, 0700); err != nil {
		return err
	}
	if _, err := os.Stat(s.configPath()); errors.Is(err, os.ErrNotExist) {
		return s.SaveConfig(model.DefaultConfig())
	}
	return nil
}

func (s *Store) LoadConfig() (model.Config, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()
	var c model.Config
	if err := readJSON(s.configPath(), &c); err != nil {
		return c, err
	}
	c = model.NormalizeConfig(c)
	return c, model.ValidateConfig(c)
}

func (s *Store) SaveConfig(c model.Config) error {
	c = model.NormalizeConfig(c)
	if err := model.ValidateConfig(c); err != nil {
		return err
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	return writeJSONAtomic(s.configPath(), c, 0600)
}

func (s *Store) LoadRuns() ([]model.Run, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()
	var runs []model.Run
	if err := readJSON(s.runsPath(), &runs); errors.Is(err, os.ErrNotExist) {
		return []model.Run{}, nil
	} else if err != nil {
		return nil, err
	}
	return runs, nil
}

func (s *Store) SaveRuns(runs []model.Run) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if len(runs) > 500 {
		runs = runs[len(runs)-500:]
	}
	return writeJSONAtomic(s.runsPath(), runs, 0600)
}

func (s *Store) configPath() string { return filepath.Join(s.dir, "config.json") }
func (s *Store) runsPath() string   { return filepath.Join(s.dir, "runs.json") }

func readJSON(path string, target any) error {
	b, err := os.ReadFile(path)
	if err != nil {
		return err
	}
	if err := json.Unmarshal(b, target); err != nil {
		return fmt.Errorf("decode %s: %w", path, err)
	}
	return nil
}

func writeJSONAtomic(path string, value any, mode os.FileMode) error {
	b, err := json.MarshalIndent(value, "", "  ")
	if err != nil {
		return err
	}
	b = append(b, '\n')
	tmp, err := os.CreateTemp(filepath.Dir(path), ".urbm-*")
	if err != nil {
		return err
	}
	tmpName := tmp.Name()
	defer os.Remove(tmpName)
	if err := tmp.Chmod(mode); err != nil {
		tmp.Close()
		return err
	}
	if _, err := tmp.Write(b); err != nil {
		tmp.Close()
		return err
	}
	if err := tmp.Sync(); err != nil {
		tmp.Close()
		return err
	}
	if err := tmp.Close(); err != nil {
		return err
	}
	return os.Rename(tmpName, path)
}
