package healthcheck

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"sync/atomic"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/connectivity"
)

const (
	serviceName = "gateway-service"

	readyBudget     = 2 * time.Second
	grpcCheckBudget = 500 * time.Millisecond
	componentStatus = "ok"
	failStatus      = "fail"
)

// Check 描述健康检查子项，失败信息会暴露给部署系统和本地排障命令。
type Check struct {
	Name    string `json:"name"`
	Status  string `json:"status"`
	Message string `json:"message,omitempty"`
}

// GRPCDependency describes one internal service connection required before the
// gateway should receive new external traffic.
type GRPCDependency struct {
	Name string
	Conn *grpc.ClientConn
}

// State 记录 gateway 接新流量所需的进程状态和 room-service 连接状态。
type State struct {
	nodeID       string
	jwtSecret    string
	dependencies []GRPCDependency

	httpServing atomic.Bool
	draining    atomic.Bool
}

// NewState 创建 gateway 健康状态所有者。
func NewState(nodeID string, jwtSecret string, room *grpc.ClientConn, dependencies ...GRPCDependency) *State {
	all := make([]GRPCDependency, 0, 1+len(dependencies))
	all = append(all, GRPCDependency{Name: "room_grpc", Conn: room})
	all = append(all, dependencies...)
	return &State{
		nodeID:       nodeID,
		jwtSecret:    jwtSecret,
		dependencies: all,
	}
}

// ServiceName 返回健康检查对外声明的服务边界名。
func (s *State) ServiceName() string {
	return serviceName
}

// NodeID 返回当前实例标识，用于定位具体 gateway 进程。
func (s *State) NodeID() string {
	return s.nodeID
}

// MarkHTTPServing 标记 gateway HTTP listener 已进入 Serve 循环。
func (s *State) MarkHTTPServing() {
	s.httpServing.Store(true)
}

// MarkHTTPStopped 标记 gateway HTTP listener 已退出 Serve 循环。
func (s *State) MarkHTTPStopped() {
	s.httpServing.Store(false)
}

// MarkDraining 让 ready 立即失败，滚动下线时负载均衡不再打新流量。
func (s *State) MarkDraining() {
	s.draining.Store(true)
}

// Live 只判断 gateway 进程 HTTP runtime 是否仍在服务，不探测 room-service。
func (s *State) Live() []Check {
	return []Check{
		boolCheck("http_server", s.httpServing.Load(), "HTTP listener is not serving"),
	}
}

// Ready 检查 gateway 接新请求所需硬依赖：JWT 配置、HTTP listener 和 room-service。
func (s *State) Ready(ctx context.Context) []Check {
	ctx, cancel := context.WithTimeout(ctx, readyBudget)
	defer cancel()

	checks := []Check{
		boolCheck("jwt_config", strings.TrimSpace(s.jwtSecret) != "", "JWT secret is empty"),
		boolCheck("http_listener", s.httpServing.Load(), "HTTP listener is not serving"),
		boolCheck("draining", !s.draining.Load(), "node is draining"),
	}

	checks = append(checks, s.grpcChecks(ctx)...)

	return checks
}

// Healthy 汇总检查项状态，HTTP status code 只看这个结果。
func Healthy(checks []Check) bool {
	for _, check := range checks {
		if check.Status != componentStatus {
			return false
		}
	}

	return true
}

func (s *State) grpcChecks(ctx context.Context) []Check {
	results := make([]Check, len(s.dependencies))
	done := make(chan struct{})

	for index, dependency := range s.dependencies {
		index := index
		dependency := dependency
		go func() {
			name := strings.TrimSpace(dependency.Name)
			if name == "" {
				name = "grpc_dependency"
			}

			checkCtx, cancel := context.WithTimeout(ctx, grpcCheckBudget)
			defer cancel()
			if err := waitGRPCReady(checkCtx, dependency.Conn); err != nil {
				results[index] = failCheck(name, err.Error())
			} else {
				results[index] = okCheck(name)
			}
			done <- struct{}{}
		}()
	}

	for range s.dependencies {
		<-done
	}
	return results
}

func waitGRPCReady(ctx context.Context, conn *grpc.ClientConn) error {
	if conn == nil {
		return errors.New("room gRPC connection is not initialized")
	}

	for {
		state := conn.GetState()
		if state == connectivity.Ready {
			return nil
		}
		if state == connectivity.Shutdown {
			return errors.New("room gRPC connection is shutdown")
		}

		// grpc.Dial 是惰性连接；ready 探测负责触发连接并在预算内等待。
		conn.Connect()
		if !conn.WaitForStateChange(ctx, state) {
			return fmt.Errorf("room gRPC not ready before deadline: %w", ctx.Err())
		}
	}
}

func okCheck(name string) Check {
	return Check{Name: name, Status: componentStatus}
}

func failCheck(name string, message string) Check {
	return Check{Name: name, Status: failStatus, Message: message}
}

func boolCheck(name string, ok bool, message string) Check {
	if ok {
		return okCheck(name)
	}

	return failCheck(name, message)
}
