// Package terminal 提供一个仅限管理员的网页端交互式终端：在 server 宿主进程
// （部署时即 runtime 容器）内拉起一个 shell，经 WebSocket 双向桥接 stdin/stdout，
// 主要用途是运维（例如容器内 npm i -g 安装 ACP 客户端 CLI）。
//
// 会话纯内存、不落库：开即用、断即清，server 重启即全部销毁（沿用 run profiles
// 「运行态不持久化」的取舍）。进程树终止复用 internal/infra/proc，断连时整树清理，
// 避免 shell 派生的子孙进程（npm→node…）残留为孤儿。
//
// 跨平台：Unix 走真 PTY（github.com/creack/pty）；Windows 走降级的 stdio 管道
// 实现（无 PTY、无 resize），仅供本地开发调试，不追求生产可用。
package terminal

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

	"github.com/google/uuid"
)

// SupervisorConfig 是从 config.TerminalConfig 派生的 supervisor 子集。
type SupervisorConfig struct {
	Shell       string        // 启动的 shell，如 /bin/bash
	MaxSessions int           // 同时存在的会话上限
	KillGrace   time.Duration // 终止整组时 SIGTERM→SIGKILL 的宽限
	// EnvWhitelist 是允许从宿主进程透传给 shell 的环境变量名白名单。
	// APP_MASTER_KEY、DB DSN、git 凭据等敏感变量绝不应出现在此名单中。
	EnvWhitelist []string
}

// ErrTooManySessions 在达到 MaxSessions 上限时返回，由 Handler 翻译为 errs code。
var ErrTooManySessions = errSentinel("terminal: too many active sessions")

type errSentinel string

func (e errSentinel) Error() string { return string(e) }

// Session 是单个终端会话的运行时句柄。pty 持有平台相关的 shell 进程与 IO，
// done 在进程退出（monitor）时关闭。
type Session struct {
	ID   uuid.UUID
	pty  *ptySession
	done chan struct{} // monitor 在 shell 退出后 close
}

// Read 从 shell 读输出（PTY master / 管道读端）。
func (s *Session) Read(p []byte) (int, error) { return s.pty.rw.Read(p) }

// Write 向 shell 写输入（PTY master / stdin 管道）。
func (s *Session) Write(p []byte) (int, error) { return s.pty.rw.Write(p) }

// Resize 调整终端窗口尺寸（Windows 降级实现为 no-op）。
func (s *Session) Resize(rows, cols uint16) error { return s.pty.resize(rows, cols) }

// Done 在 shell 进程退出后被关闭，供调用方感知进程自行结束（如用户输入 exit）。
func (s *Session) Done() <-chan struct{} { return s.done }

// Supervisor 是终端会话总管。被动结构：Open 时起 monitor goroutine，无主循环。
// 并发骨架照搬 run.RunSupervisor 已被测试验证的不变量（sessions map 写锁仅在
// Open 插入 / monitor 删除时持有；done 严格 close-once，monitor 唯一持有）。
type Supervisor struct {
	mu       sync.RWMutex
	sessions map[uuid.UUID]*Session

	cfg SupervisorConfig
	log *slog.Logger
}

// NewSupervisor 构造空 Supervisor。
func NewSupervisor(cfg SupervisorConfig, log *slog.Logger) *Supervisor {
	if log == nil {
		log = slog.Default()
	}
	return &Supervisor{
		sessions: map[uuid.UUID]*Session{},
		cfg:      cfg,
		log:      log,
	}
}

// OpenParams 描述一次终端会话的初始参数。
type OpenParams struct {
	Rows uint16
	Cols uint16
}

// Open 启动一个 shell 会话并托管。达到 MaxSessions 上限时返回 ErrTooManySessions。
func (s *Supervisor) Open(p OpenParams) (*Session, error) {
	s.mu.RLock()
	full := len(s.sessions) >= s.cfg.MaxSessions
	s.mu.RUnlock()
	if full {
		return nil, ErrTooManySessions
	}

	rows, cols := p.Rows, p.Cols
	if rows == 0 {
		rows = defaultRows
	}
	if cols == 0 {
		cols = defaultCols
	}

	ps, err := startPTY(s.cfg.Shell, buildEnv(s.cfg.EnvWhitelist), rows, cols)
	if err != nil {
		return nil, err
	}

	id, err := uuid.NewRandom()
	if err != nil {
		ps.terminate()
		return nil, err
	}
	sess := &Session{ID: id, pty: ps, done: make(chan struct{})}

	s.mu.Lock()
	s.sessions[id] = sess
	s.mu.Unlock()

	go s.monitor(sess)
	return sess, nil
}

// monitor 阻塞等待 shell 退出后回收：关闭 done、移除映射、释放平台句柄。
func (s *Supervisor) monitor(sess *Session) {
	_ = sess.pty.cmd.Wait() // reap，避免僵尸进程
	close(sess.done)
	s.mu.Lock()
	delete(s.sessions, sess.ID)
	s.mu.Unlock()
	sess.pty.group.Close()
}

// Close 终止指定会话（整树）。幂等：会话已不存在时直接返回。阻塞直到 done 关闭
// 或宽限耗尽。
func (s *Supervisor) Close(ctx context.Context, id uuid.UUID) {
	s.mu.RLock()
	sess, ok := s.sessions[id]
	s.mu.RUnlock()
	if !ok {
		return
	}
	sess.pty.group.TerminateGroup(sess.done, s.cfg.KillGrace)
	select {
	case <-sess.done:
	case <-ctx.Done():
	}
}

// Count 返回当前活跃会话数。
func (s *Supervisor) Count() int {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return len(s.sessions)
}

// CloseAll 在服务关停时终止全部会话。
func (s *Supervisor) CloseAll(ctx context.Context) {
	s.mu.RLock()
	ids := make([]uuid.UUID, 0, len(s.sessions))
	for id := range s.sessions {
		ids = append(ids, id)
	}
	s.mu.RUnlock()

	var wg sync.WaitGroup
	for _, id := range ids {
		wg.Add(1)
		go func(sid uuid.UUID) {
			defer wg.Done()
			s.Close(ctx, sid)
		}(id)
	}

	done := make(chan struct{})
	go func() {
		wg.Wait()
		close(done)
	}()
	select {
	case <-done:
	case <-ctx.Done():
	}
}
