// Command bj-license-server — онлайн-сервис учёта и отзыва лицензий.
//
// Базовая модель лицензирования офлайновая: bj-server проверяет подпись и
// срок сам. Этот сервер нужен для:
//   - реестра выданных лицензий (учёт);
//   - ОТЗЫВА (revocation) до окончания срока;
//   - проверки клиентом «не отозвана ли» (опциональный online-чек).
//
// Хранилище — JSON-файл со списком отозванных ID (для каркаса; в проде —
// PostgreSQL). API:
//
//	GET  /v1/check?id=<license-id>   → {"revoked":bool}
//	GET  /healthz
//
// Управление отзывом — правкой файла revoked.json (или будущим admin API).
package main

import (
	"encoding/json"
	"flag"
	"log"
	"net/http"
	"os"
	"sync"
	"time"
)

type store struct {
	mu      sync.RWMutex
	path    string
	revoked map[string]bool
}

func newStore(path string) *store {
	s := &store{path: path, revoked: map[string]bool{}}
	s.load()
	return s
}

func (s *store) load() {
	s.mu.Lock()
	defer s.mu.Unlock()
	b, err := os.ReadFile(s.path)
	if err != nil {
		return // файла нет — пустой список
	}
	var ids []string
	if err := json.Unmarshal(b, &ids); err != nil {
		log.Printf("license-server: разбор %s: %v", s.path, err)
		return
	}
	s.revoked = map[string]bool{}
	for _, id := range ids {
		s.revoked[id] = true
	}
	log.Printf("license-server: загружено отозванных лицензий: %d", len(s.revoked))
}

func (s *store) isRevoked(id string) bool {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.revoked[id]
}

func main() {
	addr := flag.String("addr", ":8091", "адрес прослушивания")
	file := flag.String("revoked", "./revoked.json", "JSON-файл со списком отозванных license ID")
	flag.Parse()

	st := newStore(*file)

	// Перечитываем файл отзывов раз в минуту (горячее применение).
	go func() {
		t := time.NewTicker(time.Minute)
		defer t.Stop()
		for range t.C {
			st.load()
		}
	}()

	mux := http.NewServeMux()
	mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("ok")) })
	mux.HandleFunc("/v1/check", func(w http.ResponseWriter, r *http.Request) {
		id := r.URL.Query().Get("id")
		if id == "" {
			http.Error(w, "id required", http.StatusBadRequest)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]bool{"revoked": st.isRevoked(id)})
	})

	log.Printf("license-server: слушаю %s, отзывы из %s", *addr, *file)
	srv := &http.Server{Addr: *addr, Handler: mux, ReadHeaderTimeout: 10 * time.Second}
	log.Fatal(srv.ListenAndServe())
}
