// Command publish-mirrors pushes a signed distribution document to ≥3 mirrors
// and verifies every mirror serves byte-identical content (doc/05 §1/§5).
//
// Subcommands:
//
//	publish -in doc.json -config mirrors.json [-name endpoints.v1.json] [-verify-only]
//	fetch   -config mirrors.json -name endpoints.v1.json [-keys "id=b64"] [-type endpoints|notices]
//
// The mirrors config lists each destination's local content root (dir) and an
// optional fetch_url used for read-back verification / failover fetch. The
// per-mirror sync_cmd (e.g. wrangler / aws s3 cp) is recorded for the operator
// but never executed here — publishing writes the object into dir, and the
// deploy ships dir to its platform out of band.
package main

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

	"github.com/wangjia/pangolin/infra/domains/tools/internal/endpoint"
	"github.com/wangjia/pangolin/infra/domains/tools/internal/mirror"
	"github.com/wangjia/pangolin/infra/domains/tools/internal/sign"
)

func main() {
	if len(os.Args) < 2 {
		usage()
		os.Exit(2)
	}
	var err error
	switch os.Args[1] {
	case "publish":
		err = cmdPublish(os.Args[2:])
	case "fetch":
		err = cmdFetch(os.Args[2:])
	case "-h", "--help", "help":
		usage()
		return
	default:
		fmt.Fprintf(os.Stderr, "unknown subcommand %q\n", os.Args[1])
		usage()
		os.Exit(2)
	}
	if err != nil {
		fmt.Fprintln(os.Stderr, "error:", err)
		os.Exit(1)
	}
}

func usage() {
	fmt.Fprint(os.Stderr, `publish-mirrors — push signed docs to ≥3 mirrors and verify consistency

  publish -in doc.json -config mirrors.json [-name endpoints.v1.json] [-verify-only]
  fetch   -config mirrors.json -name endpoints.v1.json [-keys "id=b64"] [-type endpoints|notices]
`)
}

func loadConfig(path string) (mirror.Config, error) {
	var cfg mirror.Config
	raw, err := os.ReadFile(path)
	if err != nil {
		return cfg, err
	}
	if err := json.Unmarshal(raw, &cfg); err != nil {
		return cfg, fmt.Errorf("parse mirrors config: %w", err)
	}
	if len(cfg.Mirrors) < 3 {
		return cfg, fmt.Errorf("at least 3 mirrors required, got %d", len(cfg.Mirrors))
	}
	return cfg, nil
}

func cmdPublish(args []string) error {
	fs := flag.NewFlagSet("publish", flag.ContinueOnError)
	in := fs.String("in", "", "signed document to publish")
	cfgPath := fs.String("config", "", "mirrors config JSON")
	name := fs.String("name", "", "published object name (default: basename of -in)")
	verifyOnly := fs.Bool("verify-only", false, "skip writing; only verify existing mirror content matches -in")
	if err := fs.Parse(args); err != nil {
		return err
	}
	if *in == "" || *cfgPath == "" {
		return errors.New("-in and -config are required")
	}
	cfg, err := loadConfig(*cfgPath)
	if err != nil {
		return err
	}
	content, err := os.ReadFile(*in)
	if err != nil {
		return err
	}
	objectName := *name
	if objectName == "" {
		objectName = filepath.Base(*in)
	}
	want := mirror.SHA256Hex(content)

	if !*verifyOnly {
		results, perr := mirror.Publish(content, objectName, cfg.Mirrors)
		for _, r := range results {
			if r.Err != nil {
				fmt.Printf("  ✗ %-16s %v\n", r.Name, r.Err)
			} else {
				fmt.Printf("  ✓ %-16s %s  sha256=%s\n", r.Name, r.Path, r.SHA256)
			}
		}
		if perr != nil {
			return fmt.Errorf("publish had failures: %w", perr)
		}
	}

	if err := mirror.VerifyConsistency(objectName, cfg.Mirrors, want); err != nil {
		return err
	}
	fmt.Printf("consistency OK: %d/%d mirrors serve sha256=%s\n", len(cfg.Mirrors), len(cfg.Mirrors), want)
	return nil
}

func cmdFetch(args []string) error {
	fs := flag.NewFlagSet("fetch", flag.ContinueOnError)
	cfgPath := fs.String("config", "", "mirrors config JSON")
	name := fs.String("name", "", "object name to fetch")
	keys := fs.String("keys", "", "optional key ring id=base64pub[,...] to verify signature on fetch")
	docType := fs.String("type", "endpoints", "payload type when -keys given: endpoints|notices")
	out := fs.String("out", "", "write fetched content here (default: stdout)")
	if err := fs.Parse(args); err != nil {
		return err
	}
	if *cfgPath == "" || *name == "" {
		return errors.New("-config and -name are required")
	}
	cfg, err := loadConfig(*cfgPath)
	if err != nil {
		return err
	}

	var validate func([]byte) error
	if *keys != "" {
		ring, err := sign.ParseKeyRing([]string{*keys})
		if err != nil {
			return err
		}
		validate = func(b []byte) error {
			if *docType == "endpoints" {
				_, _, e := endpoint.VerifyDocument(b, ring, 0)
				return e
			}
			env, e := sign.Parse(b)
			if e != nil {
				return e
			}
			return sign.Verify(env, ring)
		}
	}

	content, winner, err := mirror.Fetch(*name, cfg.Mirrors, validate)
	if err != nil {
		return err
	}
	fmt.Fprintf(os.Stderr, "fetched from mirror %q\n", winner)
	if *out == "" {
		_, err = os.Stdout.Write(content)
		return err
	}
	return os.WriteFile(*out, content, 0o644)
}
