package main

import (
	"context"
	"fmt"
	"log/slog"
	"os"
	"os/signal"
	"syscall"

	"time"

	"github.com/jackc/pgx/v5/pgxpool"

	workerbacktest "git.toki-labs.com/toki/alt/services/worker/internal/backtest"
	"git.toki-labs.com/toki/alt/services/worker/internal/config"
	"git.toki-labs.com/toki/alt/services/worker/internal/jobs"
	"git.toki-labs.com/toki/alt/services/worker/internal/marketdata/importer"
	"git.toki-labs.com/toki/alt/services/worker/internal/papertrading"
	"git.toki-labs.com/toki/alt/services/worker/internal/providers/kis"
	"git.toki-labs.com/toki/alt/services/worker/internal/socket"
	"git.toki-labs.com/toki/alt/services/worker/internal/storage/postgres"
)

func main() {
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()

	cfg := config.Load()
	runner := jobs.NewRunner()
	jobs.RegisterBuiltins(runner)

	// Store-backed socket surfaces are optional at process startup. If the pool
	// cannot be constructed the worker still serves hello, while market/backtest
	// handlers report unavailable instead of panicking.
	var deps socket.Deps
	pool, err := pgxpool.New(ctx, cfg.DatabaseURL)
	if err != nil {
		slog.Error("failed to create database pool; store-backed socket surfaces disabled", "error", err)
	} else {
		defer pool.Close()
		deps = storeBackedDeps(runner, postgres.NewStore(pool))
	}

	server := socket.NewServer(cfg, deps)
	if err := server.Start(ctx); err != nil {
		slog.Error("failed to start worker socket server", "error", err)
		os.Exit(1)
	}

	slog.Info("worker ready",
		"redis_key_prefix", cfg.RedisKeyPrefix,
		"worker_queue", cfg.WorkerQueue,
		"handlers", runner.Len(),
		"backtest_read_surface", deps.Analysis != nil,
		"backtest_start_surface", deps.Starter != nil,
		"market_read_surface", deps.Instruments != nil && deps.Bars != nil,
		"market_import_surface", deps.DailyBarImporter != nil,
		"addr", fmt.Sprintf("%s:%d%s", cfg.Host, cfg.Port, cfg.SocketPath),
	)

	<-ctx.Done()
	if err := server.Stop(); err != nil {
		slog.Error("failed to stop worker socket server", "error", err)
		os.Exit(1)
	}
}

// storeBackedDeps wires every store-backed socket dependency around a single
// store, including the backtest execution rail. It is factored out of main so a
// unit test can assert the backtest start/read surfaces are wired without a live
// database. Backtest execution reads imported daily bars from storage and runs
// the bundled strategy resolver; the starter records the pending run and hands
// execution to the same runner the import jobs use, keeping a single async job
// rail rather than a second execution engine.
func storeBackedDeps(runner *jobs.Runner, store *postgres.Store) socket.Deps {
	kisProvider := kis.NewLiveProvider(kis.NewClient(kis.ConfigFromEnv()))
	// One importer instance backs both the async job handler and the socket
	// import command so the operator-facing command and scheduled jobs share the
	// same provider/store wiring.
	kisImporter := importer.New(kisProvider, store)
	jobs.RegisterDailyBarImportHandler(runner, kis.Capability(), kisImporter)

	barSource := workerbacktest.NewStorageBarSource(store, store)
	strategyPort := workerbacktest.NewBuiltInStrategyPort()
	engine := workerbacktest.NewEngine(barSource, strategyPort, store)
	jobs.RegisterRunBacktestHandler(runner, store, engine, time.Now)
	starter := jobs.NewBacktestStarter(runner, store)

	// Paper trading reuses the same imported-bar source and bundled strategy
	// resolver as backtest, but runs synchronously into a process-local
	// in-memory state keyed by account id (no durable store for readiness).
	paperEngine := papertrading.NewEngine(barSource, strategyPort)
	paperService := papertrading.NewService(paperEngine, time.Now)

	return socket.Deps{
		Starter:          starter,
		Analysis:         store,
		Results:          store,
		Instruments:      store,
		Bars:             store,
		DailyBarImporter: kisImporter,
		Paper:            paperService,
	}
}
