package sshmanager

import (
	"bytes"
	"context"
	"fmt"
	"net"
	"os"
	"path/filepath"
	"strings"
	"time"

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

type ConnResult struct {
	Success    bool
	Output     string
	Error      string
	Fingerprint string
}

func TestSSHConnection(ctx context.Context, host string, port int, user, privKeyPath, knownHostsPath string, strictHostKeyChecking bool) (*ConnResult, error) {
	addr := fmt.Sprintf("%s:%d", host, port)

	auths := []ssh.AuthMethod{}
	if privKeyPath != "" {
		key, err := os.ReadFile(privKeyPath)
		if err != nil {
			return nil, fmt.Errorf("reading private key: %w", err)
		}
		signer, err := ssh.ParsePrivateKey(key)
		if err != nil {
			return nil, fmt.Errorf("parsing private key: %w", err)
		}
		auths = append(auths, ssh.PublicKeys(signer))
	}

	hostKeyPolicy := ssh.InsecureIgnoreHostKey()
	if strictHostKeyChecking && knownHostsPath != "" {
		hostKeyCallback, err := getHostKeyCallback(knownHostsPath, host, port)
		if err != nil {
			return &ConnResult{Success: false, Error: fmt.Sprintf("known_hosts: %v", err)}, nil
		}
		hostKeyPolicy = hostKeyCallback
	}

	cfg := &ssh.ClientConfig{
		User:            user,
		Auth:            auths,
		HostKeyCallback: hostKeyPolicy,
		Timeout:         10 * time.Second,
	}

	ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
	defer cancel()

	conn, err := ssh.Dial("tcp", addr, cfg)
	if err != nil {
		if strings.Contains(err.Error(), "known_hosts") || strings.Contains(err.Error(), "host key") {
			return &ConnResult{
				Success: false,
				Error:   fmt.Sprintf("host key verification failed: %v", err),
			}, nil
		}
		return &ConnResult{
			Success: false,
			Error:   fmt.Sprintf("connection failed: %v", err),
		}, nil
	}
	defer conn.Close()

	session, err := conn.NewSession()
	if err != nil {
		return &ConnResult{Success: false, Error: fmt.Sprintf("session: %v", err)}, nil
	}
	defer session.Close()

	var stdout, stderr bytes.Buffer
	session.Stdout = &stdout
	session.Stderr = &stderr

	if err := session.Run("echo ok && uname -a"); err != nil {
		return &ConnResult{
			Success: false,
			Error:   fmt.Sprintf("exec: %v, stderr: %s", err, stderr.String()),
		}, nil
	}

	return &ConnResult{
		Success: true,
		Output:  stdout.String(),
	}, nil
}

func getHostKeyCallback(knownHostsPath, host string, port int) (ssh.HostKeyCallback, error) {
	return ssh.HostKeyCallback(func(hostname string, remote net.Addr, key ssh.PublicKey) error {
		kh, err := GetKnownHost(filepath.Dir(knownHostsPath), host, port)
		if err != nil {
			return fmt.Errorf("checking known_hosts: %w", err)
		}
		if kh == nil {
			return fmt.Errorf("host key not found in known_hosts: %s", hostname)
		}
		return nil
	}), nil
}
