package app

import (
	"context"
	"errors"
	"log/slog"
	"net"
	"sync"
	"time"

	"google.golang.org/grpc"
	robotv1 "hyapp.local/api/proto/robot/v1"
	"hyapp/pkg/grpchealth"
	"hyapp/pkg/healthhttp"
	"hyapp/pkg/logx"
	servicegrpc "hyapp/pkg/servicekit/grpcserver"
	servicehealth "hyapp/pkg/servicekit/health"
	"hyapp/services/robot-service/internal/config"
	"hyapp/services/robot-service/internal/service/gamerobot"
	"hyapp/services/robot-service/internal/service/roomrobot"
	mysqlstorage "hyapp/services/robot-service/internal/storage/mysql"
	grpcserver "hyapp/services/robot-service/internal/transport/grpc"
)

// App 持有 robot-service 进程生命周期；首版只管理机器人账号池，不主动驱动游戏局或房间状态机。
type App struct {
	server     *grpc.Server
	listener   net.Listener
	health     *grpchealth.ServingChecker
	healthHTTP *healthhttp.Server
	repo       *mysqlstorage.Repository
	closeOnce  sync.Once
}

func New(cfg config.Config) (*App, error) {
	startupCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	repo, err := mysqlstorage.Open(startupCtx, cfg.MySQLDSN)
	if err != nil {
		return nil, err
	}
	migration, err := repo.MigrateLegacyGameRobots(startupCtx, cfg.LegacyGameDatabase)
	if err != nil {
		_ = repo.Close()
		return nil, err
	}
	if migration.Applied {
		logx.Info(startupCtx, "robot_legacy_game_robot_migration_applied",
			slog.String("legacy_database", migration.LegacyDatabase),
			slog.Int64("migrated_rows", migration.MigratedRows),
		)
	} else if cfg.LegacyGameDatabase != "" && !migration.AlreadyCompleted && !migration.SourceFound {
		logx.Warn(startupCtx, "robot_legacy_game_robot_source_missing",
			slog.String("legacy_database", migration.LegacyDatabase),
		)
	}
	listener, err := net.Listen("tcp", cfg.GRPCAddr)
	if err != nil {
		_ = repo.Close()
		return nil, err
	}

	server := servicegrpc.New("robot-service")
	transport := grpcserver.NewServer(gamerobot.New(repo), roomrobot.New(repo))
	robotv1.RegisterGameRobotServiceServer(server, transport)
	robotv1.RegisterRoomRobotServiceServer(server, transport)
	health, healthHTTP, err := servicehealth.New(server, servicehealth.Config{
		ServiceName: "robot-service",
		HTTPAddr:    cfg.HealthHTTPAddr,
		NodeID:      cfg.NodeID,
		Dependencies: []grpchealth.Dependency{{
			Name:  "mysql",
			Check: repo.Ping,
		}},
	})
	if err != nil {
		_ = listener.Close()
		_ = repo.Close()
		return nil, err
	}

	return &App{server: server, listener: listener, health: health, healthHTTP: healthHTTP, repo: repo}, nil
}

func (a *App) Run() error {
	a.runHealthHTTP()
	a.health.MarkServing()
	err := a.server.Serve(a.listener)
	a.health.MarkStopped()
	if errors.Is(err, grpc.ErrServerStopped) {
		return nil
	}
	return err
}

func (a *App) Close() {
	a.closeOnce.Do(func() {
		a.health.MarkDraining()
		servicegrpc.GracefulStop(a.server, 15*time.Second)
		a.closeHealthHTTP()
		if a.repo != nil {
			_ = a.repo.Close()
		}
	})
}

func (a *App) runHealthHTTP() {
	if a.healthHTTP == nil {
		return
	}
	go func() {
		if err := a.healthHTTP.Run(); err != nil && !errors.Is(err, context.Canceled) {
			logx.Error(context.Background(), "robot_health_http_failed", err)
		}
	}()
}

func (a *App) closeHealthHTTP() {
	if a.healthHTTP == nil {
		return
	}
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()
	if err := a.healthHTTP.Close(ctx); err != nil {
		logx.Error(context.Background(), "robot_health_http_shutdown_failed", err)
	}
}
