package main

import (
	"context"
	"errors"
	"flag"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/nexuscrm/audit"
	"github.com/nexuscrm/backend/internal/runtimeconfig"
	"github.com/nexuscrm/backend/internal/runtimeworker"
	"github.com/nexuscrm/persistence"
)

var version = "dev"

func main() {
	ctx, stopSignals := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
	defer stopSignals()
	config, err := runtimeconfig.LoadWorker(os.LookupEnv)
	if err != nil {
		log.Fatal(err)
	}
	if len(os.Args) > 1 {
		if os.Args[1] != "repair" {
			log.Fatalf("runtime-worker: unknown command %q", os.Args[1])
		}
		if err := runRepair(ctx, config, os.Args[2:]); err != nil {
			log.Fatal(err)
		}
		log.Print("runtime-worker repair accepted for replay")
		return
	}
	// Keep startup cancelable by process signals, but let the deployment
	// readiness gate own its time budget. A fixed process-local deadline can
	// kill valid cold starts before a remote TiDB-backed graph is assembled.
	process, db, err := runtimeworker.AssembleProduction(ctx, version, config)
	if err != nil {
		log.Fatal(err)
	}
	if err := process.Start(); err != nil {
		_ = db.Close()
		log.Fatal(err)
	}
	log.Printf("runtime-worker started: admin=%s work_family=%s deployment=%s epoch=%d",
		process.AdminAddress(), config.Role.WorkFamily, config.Role.DeploymentID, config.Role.OwnershipEpoch)
	var processErr error
	select {
	case <-ctx.Done():
	case processErr = <-wait(process):
	}
	shutdownTimeout, err := time.ParseDuration(config.Role.ShutdownTimeout)
	if err != nil {
		log.Printf("runtime-worker invalid shutdown timeout after validated load: %v", err)
		shutdownTimeout = 10 * time.Second
	}
	shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
	defer cancel()
	if err := process.Stop(shutdownCtx); err != nil {
		log.Printf("runtime-worker shutdown: %v", err)
	}
	if err := db.Close(); err != nil && processErr == nil {
		processErr = err
	}
	if processErr != nil {
		log.Fatal(processErr)
	}
}

func runRepair(ctx context.Context, config runtimeconfig.Worker, args []string) error {
	flags := flag.NewFlagSet("runtime-worker repair", flag.ContinueOnError)
	outboxID := flags.Int64("outbox-id", 0, "exact poisoned outbox row ID")
	expectedClass := flags.String("expected-class", "", "exact poison class")
	expectedAttempts := flags.Int("expected-attempts", -1, "exact observed attempt count")
	rowID := flags.String("row-id", "", "exact poisoned metadata or file-lifecycle row ID")
	expectedRetryCount := flags.Int("expected-retry-count", -1, "exact observed durable retry count")
	expectedLastErrorSHA256 := flags.String("expected-last-error-sha256", "", "SHA-256 of the exact observed durable error")
	repairID := flags.String("repair-id", "", "stable operator repair identity")
	reason := flags.String("reason", "", "durable repair reason")
	if err := flags.Parse(args); err != nil {
		return err
	}
	if flags.NArg() != 0 {
		return errors.New("runtime-worker repair: positional arguments are not allowed")
	}
	repairCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
	defer cancel()
	return runtimeworker.RepairProduction(repairCtx, version, config, runtimeworker.RepairRequest{
		Audit: audit.OutboxRepair{
			OutboxRowID: *outboxID, ExpectedClass: audit.PoisonClass(*expectedClass),
			ExpectedAttempts: *expectedAttempts, RepairID: *repairID, Reason: *reason,
		},
		Poison: persistence.RuntimePoisonRepair{
			RowID: *rowID, ExpectedRetryCount: *expectedRetryCount, ExpectedLastErrorSHA256: *expectedLastErrorSHA256,
			RepairID: *repairID, Reason: *reason,
		},
	})
}

func wait(process interface{ Wait() error }) <-chan error {
	done := make(chan error, 1)
	go func() { done <- process.Wait() }()
	return done
}
