package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"time"
)

type Record struct {
	ID     string
	Amount int
}
type Snapshot struct {
	Version int
	Rows    []Record
	Total   int
}
type Manifest struct {
	Hash       [32]byte
	Bytes      int
	CapturedAt time.Time
}

func Validate(s Snapshot) error {
	if s.Version != 1 {
		return errors.New("unsupported schema")
	}
	seen := map[string]bool{}
	sum := 0
	for _, r := range s.Rows {
		if r.ID == "" || seen[r.ID] || r.Amount < 0 {
			return errors.New("invalid row")
		}
		seen[r.ID] = true
		sum += r.Amount
	}
	if sum != s.Total {
		return errors.New("business total mismatch")
	}
	return nil
}
func Backup(path string, s Snapshot, at time.Time) (Manifest, error) {
	if err := Validate(s); err != nil {
		return Manifest{}, err
	}
	data, err := json.Marshal(s)
	if err != nil {
		return Manifest{}, err
	}
	if err = os.WriteFile(path, data, 0600); err != nil {
		return Manifest{}, err
	}
	return Manifest{sha256.Sum256(data), len(data), at}, nil
}
func Restore(src, dst string, m Manifest) (Snapshot, error) {
	data, err := os.ReadFile(src)
	if err != nil {
		return Snapshot{}, err
	}
	if len(data) != m.Bytes || sha256.Sum256(data) != m.Hash {
		return Snapshot{}, errors.New("checksum mismatch")
	}
	var s Snapshot
	dec := json.NewDecoder(bytes.NewReader(data))
	dec.DisallowUnknownFields()
	if err = dec.Decode(&s); err != nil {
		return Snapshot{}, err
	}
	var extra any
	if err = dec.Decode(&extra); err != io.EOF {
		return Snapshot{}, errors.New("trailing data")
	}
	if err = Validate(s); err != nil {
		return Snapshot{}, err
	}
	f, err := os.CreateTemp(filepath.Dir(dst), "restore-*")
	if err != nil {
		return Snapshot{}, err
	}
	tmp := f.Name()
	defer os.Remove(tmp)
	if _, err = f.Write(data); err != nil {
		f.Close()
		return Snapshot{}, err
	}
	if err = f.Sync(); err != nil {
		f.Close()
		return Snapshot{}, err
	}
	if err = f.Close(); err != nil {
		return Snapshot{}, err
	}
	if err = os.Rename(tmp, dst); err != nil {
		return Snapshot{}, err
	}
	return s, nil
}
func main() {
	if err := run(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}
func run() error {
	dir, err := os.MkdirTemp("", "restore-lab-*")
	if err != nil {
		return err
	}
	defer os.RemoveAll(dir)
	backup := filepath.Join(dir, "snapshot.json")
	dst := filepath.Join(dir, "restored.json")
	at := time.Date(2026, 10, 13, 12, 0, 0, 0, time.UTC)
	live := Snapshot{1, []Record{{"a", 10}, {"b", 20}}, 30}
	m, err := Backup(backup, live, at)
	if err != nil {
		return err
	}
	replica := live
	live = Snapshot{Version: 1}
	replica = live
	fmt.Println("after replicated logical delete, live rows:", len(live.Rows), "replica rows:", len(replica.Rows))
	restored, err := Restore(backup, dst, m)
	if err != nil {
		return err
	}
	fmt.Println("restored rows:", len(restored.Rows), "total:", restored.Total)
	if err = os.WriteFile(backup, []byte("corrupted fixture"), 0600); err != nil {
		return err
	}
	_, err = Restore(backup, filepath.Join(dir, "must-not-exist.json"), m)
	fmt.Println("corrupt backup rejected:", err != nil)
	incident := at.Add(2 * time.Minute)
	verified := incident.Add(11 * time.Minute)
	fmt.Println("scenario data gap:", incident.Sub(m.CapturedAt), "scenario recovery interval:", verified.Sub(incident))
	return nil
}
