package app

import (
	"context"
	"errors"
	"net"
	"sync"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	healthgrpc "google.golang.org/grpc/health/grpc_health_v1"
	activityv1 "hyapp.local/api/proto/activity/v1"
	walletv1 "hyapp.local/api/proto/wallet/v1"
	"hyapp/pkg/grpchealth"
	"hyapp/pkg/grpcshutdown"
	"hyapp/pkg/healthhttp"
	"hyapp/pkg/logx"
	"hyapp/pkg/tencentim"
	"hyapp/services/activity-service/internal/client"
	"hyapp/services/activity-service/internal/config"
	activityservice "hyapp/services/activity-service/internal/service/activity"
	broadcastservice "hyapp/services/activity-service/internal/service/broadcast"
	messageservice "hyapp/services/activity-service/internal/service/message"
	registrationrewardservice "hyapp/services/activity-service/internal/service/registrationreward"
	taskservice "hyapp/services/activity-service/internal/service/task"
	mysqlstorage "hyapp/services/activity-service/internal/storage/mysql"
	grpcserver "hyapp/services/activity-service/internal/transport/grpc"
)

// App 装配 activity-service gRPC 入口和底座依赖。
type App struct {
	server                 *grpc.Server
	listener               net.Listener
	health                 *grpchealth.ServingChecker
	healthHTTP             *healthhttp.Server
	mysqlRepo              *mysqlstorage.Repository
	broadcast              *broadcastservice.Service
	broadcastWorkerEnabled bool
	userConn               *grpc.ClientConn
	walletConn             *grpc.ClientConn
	workerCtx              context.Context
	workerStop             context.CancelFunc
	workerWG               sync.WaitGroup
	closeOnce              sync.Once
}

// New 初始化 activity-service 应用。
func New(cfg config.Config) (*App, error) {
	startupCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	// activity-service 的活动状态、消费幂等和 outbox 都必须使用 MySQL。
	repository, err := mysqlstorage.Open(startupCtx, cfg.MySQLDSN)
	if err != nil {
		return nil, err
	}

	listener, err := net.Listen("tcp", cfg.GRPCAddr)
	if err != nil {
		_ = repository.Close()
		return nil, err
	}
	userConn, err := grpc.Dial(cfg.UserServiceAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		_ = listener.Close()
		_ = repository.Close()
		return nil, err
	}
	walletConn, err := grpc.Dial(cfg.WalletServiceAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		_ = userConn.Close()
		_ = listener.Close()
		_ = repository.Close()
		return nil, err
	}

	server := grpc.NewServer(grpc.UnaryInterceptor(logx.UnaryServerInterceptor("activity-service")))
	svc := activityservice.New(activityservice.Config{NodeID: cfg.NodeID}, repository)
	messageSvc := messageservice.New(messageservice.Config{NodeID: cfg.NodeID}, repository, messageservice.WithTargetSource(client.NewGRPCUserTargetSource(userConn)))
	taskSvc := taskservice.New(repository, walletv1.NewWalletServiceClient(walletConn))
	registrationRewardSvc := registrationrewardservice.New(repository, walletv1.NewWalletServiceClient(walletConn))
	var broadcastPublisher broadcastservice.Publisher
	if cfg.TencentIM.Enabled {
		tencentClient, err := tencentim.NewRESTClient(cfg.TencentIM.RESTConfig())
		if err != nil {
			_ = walletConn.Close()
			_ = userConn.Close()
			_ = listener.Close()
			_ = repository.Close()
			return nil, err
		}
		broadcastPublisher = tencentClient
	}
	if cfg.Broadcast.Enabled && broadcastPublisher == nil {
		_ = walletConn.Close()
		_ = userConn.Close()
		_ = listener.Close()
		_ = repository.Close()
		return nil, errors.New("broadcast worker requires tencent_im.enabled")
	}
	broadcastSvc := broadcastservice.New(broadcastservice.Config{
		NodeID:                cfg.NodeID,
		SuperGiftMinValue:     cfg.Broadcast.SuperGiftMinValue,
		WorkerBatchSize:       cfg.Broadcast.WorkerBatchSize,
		WorkerLockTTL:         cfg.Broadcast.WorkerLockTTL,
		WorkerMaxRetry:        cfg.Broadcast.WorkerMaxRetry,
		WorkerPollInterval:    cfg.Broadcast.WorkerPollInterval,
		EnsureGroupsOnStartup: cfg.Broadcast.EnsureGroupsOnStartup,
	}, repository, broadcastPublisher, client.NewGRPCRegionSource(userConn))
	activityv1.RegisterActivityServiceServer(server, grpcserver.NewServer(svc))
	activityv1.RegisterMessageInboxServiceServer(server, grpcserver.NewMessageServer(messageSvc))
	activityv1.RegisterActivityCronServiceServer(server, grpcserver.NewCronServer(messageSvc))
	activityv1.RegisterTaskServiceServer(server, grpcserver.NewTaskServer(taskSvc))
	activityv1.RegisterAdminTaskServiceServer(server, grpcserver.NewAdminTaskServer(taskSvc))
	activityv1.RegisterRegistrationRewardServiceServer(server, grpcserver.NewRegistrationRewardServer(registrationRewardSvc))
	activityv1.RegisterAdminRegistrationRewardServiceServer(server, grpcserver.NewAdminRegistrationRewardServer(registrationRewardSvc))
	broadcastServer := grpcserver.NewBroadcastServer(broadcastSvc)
	activityv1.RegisterBroadcastServiceServer(server, broadcastServer)
	activityv1.RegisterRoomEventConsumerServiceServer(server, broadcastServer)
	health := grpchealth.NewServingChecker("activity-service", grpchealth.Dependency{
		Name:  "mysql",
		Check: repository.Ping,
	})
	healthgrpc.RegisterHealthServer(server, grpchealth.NewServer(health))
	healthHTTP, err := healthhttp.New(cfg.HealthHTTPAddr, cfg.NodeID, health)
	if err != nil {
		_ = walletConn.Close()
		_ = userConn.Close()
		_ = listener.Close()
		_ = repository.Close()
		return nil, err
	}
	workerCtx, workerStop := context.WithCancel(context.Background())

	return &App{
		server:                 server,
		listener:               listener,
		health:                 health,
		healthHTTP:             healthHTTP,
		mysqlRepo:              repository,
		broadcast:              broadcastSvc,
		broadcastWorkerEnabled: cfg.Broadcast.Enabled,
		userConn:               userConn,
		walletConn:             walletConn,
		workerCtx:              workerCtx,
		workerStop:             workerStop,
	}, nil
}

// Run 启动 gRPC 服务。
func (a *App) Run() error {
	a.runHealthHTTP()
	a.health.MarkServing()
	if a.broadcastWorkerEnabled && a.broadcast != nil && a.workerCtx != nil {
		a.workerWG.Go(func() {
			a.broadcast.RunWorker(a.workerCtx, broadcastservice.WorkerOptions{})
		})
	}
	err := a.server.Serve(a.listener)
	if a.workerStop != nil {
		a.workerStop()
	}
	a.workerWG.Wait()
	a.health.MarkStopped()
	if errors.Is(err, grpc.ErrServerStopped) {
		return nil
	}

	return err
}

// Close 关闭 gRPC 服务。
func (a *App) Close() {
	a.closeOnce.Do(func() {
		a.health.MarkDraining()
		if a.workerStop != nil {
			a.workerStop()
		}
		a.workerWG.Wait()
		grpcshutdown.GracefulStop(a.server, 15*time.Second)
		a.closeHealthHTTP()
		if a.userConn != nil {
			_ = a.userConn.Close()
		}
		if a.walletConn != nil {
			_ = a.walletConn.Close()
		}
		if a.mysqlRepo != nil {
			// MySQL 连接池最后关闭，保证 drain 中的查询或消费提交能完成。
			_ = a.mysqlRepo.Close()
		}
	})
}

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

func (a *App) closeHealthHTTP() {
	if a.healthHTTP == nil {
		return
	}
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	_ = a.healthHTTP.Close(ctx)
}
