package telnet

import (
	"bytes"
	"fmt"
	"net"
	"strings"
	"time"
)

// ConnTelnet 连接telnet对象
type ConnTelnet struct {
	User     string `json:"user"`     // 主机用户名
	Addr     string `json:"addr"`     // 主机地址
	Port     int64  `json:"port"`     // telnet端口
	Password string `json:"password"` // 认证密码

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

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

// NewClient 创建Telnet客户端
func (c *ConnTelnet) NewClient() (*ConnTelnet, 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)

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

	// 连接到 Telnet 服务器
	client, err := net.DialTimeout(proto, addr, c.DialTimeOut)
	if err != nil {
		return nil, err
	}

	// 进行登录
	time.Sleep(100 * time.Millisecond)
	client.Write([]byte(c.User + "\r\n"))
	time.Sleep(100 * time.Millisecond)
	client.Write([]byte(c.Password + "\r\n"))
	// fmt.Fprintln(client, c.User)
	// fmt.Fprintln(client, c.Password)

	c.Client = &client
	return c, nil
}

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

// RunCMD 执行单次命令
func (c *ConnTelnet) RunCMD(cmd string) (string, error) {
	if c.Client == nil {
		return "", fmt.Errorf("telnet client not connected")
	}
	conn := *c.Client
	var buf bytes.Buffer
	tmp := make([]byte, 1024)

	// 排空连接登录的信息
	for {
		// 设置读取超时时间为100毫秒
		conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
		n, err := conn.Read(tmp)
		if err != nil {
			// 判断是否是超时错误
			if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
				break
			}
			break
		}
		if n == 0 {
			break
		}
		buf.Write(tmp[:n])
	}
	buf.Reset()

	// 写入命令
	_, err := conn.Write([]byte(cmd))
	if err != nil {
		return "", err
	}

	// 读取本次响应命令消息
	for {
		// 设置读取超时时间为1000毫秒
		conn.SetReadDeadline(time.Now().Add(1000 * time.Millisecond))
		n, err := conn.Read(tmp)
		if err != nil {
			// 判断是否是超时错误
			if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
				break
			}
			break
		}
		if n == 0 {
			break
		}
		buf.Write(tmp[:n])
	}
	defer buf.Reset()

	c.LastResult = buf.String()
	return c.LastResult, nil
}

// NewClient 创建Telnet客户端会话对象
func (c *ConnTelnet) NewClientSession() (*TelnetClientSession, error) {
	if c.Client == nil {
		return nil, fmt.Errorf("telnet client not connected")
	}
	conn := *c.Client

	var buf bytes.Buffer
	tmp := make([]byte, 1024)
	// 排空连接登录的信息
	for {
		// 设置读取超时时间为5毫秒
		conn.SetReadDeadline(time.Now().Add(5 * time.Millisecond))
		n, err := conn.Read(tmp)
		if err != nil {
			// 判断是否是超时错误
			if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
				break
			}
			break
		}
		if n == 0 {
			break
		}
		buf.Write(tmp[:n])
	}
	buf.Reset()

	return &TelnetClientSession{
		Client: conn,
	}, nil
}

// TelnetClientSession Telnet客户端会话对象
type TelnetClientSession struct {
	Client net.Conn
}

// Close 关闭会话
func (s *TelnetClientSession) Close() {
	if s.Client != nil {
		s.Client.Close()
	}
}

// Write 写入命令 不带回车(\n)也会执行根据客户端情况
func (s *TelnetClientSession) Write(cmd string) (int, error) {
	if s.Client == nil {
		return 0, fmt.Errorf("client is nil to content write failed")
	}
	return s.Client.Write([]byte(cmd))
}

// Read 读取结果 等待一会才有结果
func (s *TelnetClientSession) Read() []byte {
	if s.Client == nil {
		return []byte{}
	}

	buf := make([]byte, 1024)
	// 设置读取超时时间为100毫秒
	s.Client.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
	_, err := s.Client.Read(buf)
	if err != nil {
		return []byte{}
	}
	return buf
}

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

	var buf bytes.Buffer
	tmp := make([]byte, 1024)
	for {
		// 设置读取超时时间为1000毫秒
		s.Client.SetReadDeadline(time.Now().Add(1000 * time.Millisecond))
		n, err := s.Client.Read(tmp)
		if err != nil {
			// 判断是否是超时错误
			if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
				break
			}
			break
		}
		if n == 0 {
			break
		}
		buf.Write(tmp[:n])
	}
	defer buf.Reset()

	return buf.String(), nil
}
