package apcssh

import (
	"bufio"
	"fmt"
	"strings"

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

// upsCmdResponse is a structure that holds all of a shell commands results
type upsCmdResponse struct {
	command    string
	code       string
	codeText   string
	resultText string
}

// cmd creates an interactive shell and executes the specified command
func (cli *Client) cmd(command string) (*upsCmdResponse, error) {
	// connect
	sshClient, err := ssh.Dial("tcp", cli.hostname, cli.sshCfg)
	if err != nil {
		return nil, fmt.Errorf("apcssh: failed to dial session (%w)", err)
	}
	defer sshClient.Close()

	session, err := sshClient.NewSession()
	if err != nil {
		return nil, fmt.Errorf("apcssh: failed to create session (%w)", err)
	}
	defer session.Close()

	// pipes to send shell command to; and to receive repsonse
	sshInput, err := session.StdinPipe()
	if err != nil {
		return nil, fmt.Errorf("apcssh: failed to make shell input pipe (%w)", err)
	}
	sshOutput, err := session.StdoutPipe()
	if err != nil {
		return nil, fmt.Errorf("apcssh: failed to make shell output pipe (%w)", err)
	}

	// make scanner to read shell continuously
	scanner := bufio.NewScanner(sshOutput)
	scanner.Split(scanAPCShell)

	// start interactive shell
	if err := session.Shell(); err != nil {
		return nil, fmt.Errorf("apcssh: failed to start shell (%w)", err)
	}
	// discard the initial shell response (login message(s) / initial shell prompt)
	for {
		if token := scanner.Scan(); token {
			_ = scanner.Bytes()
			break
		}
	}

	// send command
	_, err = fmt.Fprint(sshInput, command+"\n")
	if err != nil {
		return nil, fmt.Errorf("apcssh: failed to send shell command (%w)", err)
	}

	res := &upsCmdResponse{}
	for {
		if tkn := scanner.Scan(); tkn {
			result := string(scanner.Bytes())

			cmdIndx := strings.Index(result, "\n")
			res.command = result[:cmdIndx-1]
			result = result[cmdIndx+1:]

			codeIndx := strings.Index(result, ": ")
			res.code = result[:codeIndx]
			result = result[codeIndx+2:]

			codeTxtIndx := strings.Index(result, "\n")
			res.codeText = result[:codeTxtIndx-1]

			// avoid out of bounds if no result text
			if codeTxtIndx+1 <= len(result)-2 {
				res.resultText = result[codeTxtIndx+1 : len(result)-2]
			}
			break
		}
	}

	return res, nil
}