package ssh

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"os/user"
	"strings"
	"sync"
	"time"

	"nms_nbi/src/framework/logger"

	gossh "golang.org/x/crypto/ssh"
)

// ConnSSH 连接SSH对象
type ConnSSH struct {
	User       string `json:"user"`       // 主机用户名
	Addr       string `json:"addr"`       // 主机地址
	Port       int64  `json:"port"`       // SSH端口
	AuthMode   string `json:"authMode"`   // 认证模式（0密码 1主机私钥）
	Password   string `json:"password"`   // 认证密码
	PrivateKey string `json:"privateKey"` // 认证私钥
	PassPhrase string `json:"passPhrase"` // 认证私钥密码

	DialTimeOut time.Duration `json:"dialTimeOut"` // 连接超时断开

	Client     *gossh.Client `json:"client"`
	LastResult string        `json:"lastResult"` // 记最后一次执行命令的结果
}

// NewClient 创建SSH客户端
func (c *ConnSSH) NewClient() (*ConnSSH, error) {
	// IPV6地址协议
	proto := "tcp"
	if strings.Contains(c.Addr, ":") {
		proto = "tcp6"
		c.Addr = fmt.Sprintf("[%s]", c.Addr)
	}
	addr := fmt.Sprintf("%s:%d", c.Addr, c.Port)

	// ssh客户端配置
	config := &gossh.ClientConfig{}
	config.SetDefaults()
	config.HostKeyCallback = gossh.InsecureIgnoreHostKey()
	config.User = c.User

	// 默认等待5s
	if c.DialTimeOut == 0 {
		c.DialTimeOut = 5 * time.Second
	}
	config.Timeout = c.DialTimeOut

	// 认证模式-0密码 1私钥
	if c.AuthMode == "1" {
		var signer gossh.Signer
		var err error
		if len(c.PassPhrase) != 0 {
			signer, err = gossh.ParsePrivateKeyWithPassphrase([]byte(c.PrivateKey), []byte(c.PassPhrase))
		} else {
			signer, err = gossh.ParsePrivateKey([]byte(c.PrivateKey))
		}
		if err != nil {
			logger.Errorf("NewClient parse private key => %s", err.Error())
			return nil, err
		}
		config.Auth = []gossh.AuthMethod{gossh.PublicKeys(signer)}
	} else {
		config.Auth = []gossh.AuthMethod{gossh.Password(c.Password)}
	}

	client, err := gossh.Dial(proto, addr, config)
	if nil != err {
		logger.Errorf("NewClient dial => %s", err.Error())
		return c, err
	}
	c.Client = client
	return c, nil
}

// Close 关闭当前SSH客户端
func (c *ConnSSH) Close() {
	if c.Client != nil {
		c.Client.Close()
	}
}

// NewClientByLocalPrivate 创建SSH客户端-本地私钥(~/.ssh/id_rsa)直连
//
//	ssh.ConnSSH{
//		User: "user",
//		Addr: "192.168.x.x",
//		Port: body.Port,
//	}
func (c *ConnSSH) NewClientByLocalPrivate() (*ConnSSH, error) {
	c.Port = 22
	c.AuthMode = "1"
	usr, err := user.Current()
	if err != nil {
		logger.Errorf("NewClientByLocal get current user => %s", err.Error())
		return nil, err
	}

	// 读取用户默认的私钥文件
	keyPath := fmt.Sprintf("%s/.ssh/id_rsa", usr.HomeDir)
	key, err := os.ReadFile(keyPath)
	if err != nil {
		logger.Errorf("NewClientByLocal [%s] read private key => %s", usr.Username, err.Error())
		return nil, err
	}
	c.PrivateKey = string(key)
	return c.NewClient()
}

// RunCMD 执行单次命令
func (c *ConnSSH) RunCMD(cmd string) (string, error) {
	if c.Client == nil {
		if _, err := c.NewClient(); err != nil {
			return "", err
		}
	}
	session, err := c.Client.NewSession()
	if err != nil {
		logger.Errorf("RunCMD failed to create session: => %s", err.Error())
		return "", err
	}
	defer session.Close()
	buf, err := session.CombinedOutput(cmd)
	if err != nil {
		logger.Errorf("RunCMD failed run command: => %s", err.Error())
	}
	c.LastResult = string(buf)
	return c.LastResult, err
}

// NewClient 创建SSH客户端会话对象
func (c *ConnSSH) NewClientSession(cols, rows int) (*SSHClientSession, error) {
	sshSession, err := c.Client.NewSession()
	if err != nil {
		return nil, err
	}

	stdin, err := sshSession.StdinPipe()
	if err != nil {
		return nil, err
	}

	comboWriter := new(singleWriter)
	sshSession.Stdout = comboWriter
	sshSession.Stderr = comboWriter

	modes := gossh.TerminalModes{
		gossh.ECHO:          1,
		gossh.TTY_OP_ISPEED: 14400,
		gossh.TTY_OP_OSPEED: 14400,
	}

	if err := sshSession.RequestPty("xterm", rows, cols, modes); err != nil {
		return nil, err
	}

	if err := sshSession.Shell(); err != nil {
		return nil, err
	}

	return &SSHClientSession{
		Stdin:   stdin,
		Stdout:  comboWriter,
		Session: sshSession,
	}, nil
}

// SSHClientSession SSH客户端会话对象
type SSHClientSession struct {
	Stdin   io.WriteCloser
	Stdout  *singleWriter
	Session *gossh.Session
}

// Close 关闭会话
func (s *SSHClientSession) Close() {
	if s.Stdin != nil {
		s.Stdin.Close()
	}
	if s.Stdout != nil {
		s.Stdout = nil
	}
	if s.Session != nil {
		s.Session.Close()
	}
}

// Write 写入命令 回车(\n)才会执行
func (s *SSHClientSession) Write(cmd string) (int, error) {
	if s.Stdin == nil {
		return 0, fmt.Errorf("ssh client session is nil to content write failed")
	}
	return s.Stdin.Write([]byte(cmd))
}

// Read 读取结果 等待一会才有结果
func (s *SSHClientSession) Read() []byte {
	if s.Stdout == nil {
		return []byte{}
	}
	// time.Sleep(300 * time.Millisecond)
	bs := s.Stdout.Bytes()
	if len(bs) > 0 {
		s.Stdout.Reset()
		return bs
	}
	return []byte{}
}

// CombinedOutput 发送命令带结果返回
func (s *SSHClientSession) CombinedOutput(cmd string) (string, error) {
	n, err := s.Write(cmd)
	if n == 0 || err != nil {
		return "", err
	}
	return string(s.Read()), nil
}

// singleWriter SSH客户端会话消息
type singleWriter struct {
	b  bytes.Buffer
	mu sync.Mutex
}

func (w *singleWriter) Write(p []byte) (int, error) {
	w.mu.Lock()
	defer w.mu.Unlock()
	return w.b.Write(p)
}
func (w *singleWriter) Bytes() []byte {
	w.mu.Lock()
	defer w.mu.Unlock()
	return w.b.Bytes()
}
func (w *singleWriter) Reset() {
	w.mu.Lock()
	defer w.mu.Unlock()
	w.b.Reset()
}
