package proxmox

import (
	"bytes"
	"context"
	"crypto/ed25519"
	"crypto/rand"
	"errors"
	"log/slog"
	"net"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"testing"

	"git.cloudinit.dev/coreci/orca/internal/certpaths"
	"time"

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

type fakeSSHServer struct {
	listener net.Listener
	config   *ssh.ServerConfig
	done     chan struct{}

	mu                  sync.Mutex
	state               map[string]string
	authDir             string
	forceSudoersInvalid bool
	hostSigner          ssh.Signer
}

func newFakeSSHServer(t *testing.T) *fakeSSHServer {
	t.Helper()
	_, priv, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatalf("ed25519 gen: %v", err)
	}
	hostSigner, err := ssh.NewSignerFromKey(priv)
	if err != nil {
		t.Fatalf("ssh signer: %v", err)
	}
	config := &ssh.ServerConfig{
		PasswordCallback: func(c ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
			if string(password) != "pw" {
				return nil, errors.New("invalid password")
			}
			return nil, nil
		},
		PublicKeyCallback: func(c ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
			// Accept any public key for testing (the bootstrap deploys
			// the orca key to authorized_keys in a prior step, but the
			// fake server skips that deployment step).
			return nil, nil
		},
	}
	config.AddHostKey(hostSigner)

	ln, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("listen: %v", err)
	}
	srv := &fakeSSHServer{
		listener:   ln,
		config:     config,
		done:       make(chan struct{}),
		state:      make(map[string]string),
		authDir:    t.TempDir(),
		hostSigner: hostSigner,
	}
	go srv.serve()
	return srv
}

func (s *fakeSSHServer) addr() string { return s.listener.Addr().String() }

// hostPublicKey returns the server's SSH host public key. Used by
// callback tests to compute the pinned fingerprint the operator would
// supply, and to feed the callback the exact key the server presents.
func (s *fakeSSHServer) hostPublicKey() ssh.PublicKey {
	if s.hostSigner == nil {
		return nil
	}
	return s.hostSigner.PublicKey()
}

func (s *fakeSSHServer) serve() {
	for {
		conn, err := s.listener.Accept()
		if err != nil {
			close(s.done)
			return
		}
		go s.handle(conn)
	}
}

func (s *fakeSSHServer) handle(netConn net.Conn) {
	defer netConn.Close()
	_, chans, reqs, err := ssh.NewServerConn(netConn, s.config)
	if err != nil {
		return
	}
	go ssh.DiscardRequests(reqs)
	for newChan := range chans {
		if newChan.ChannelType() != "session" {
			newChan.Reject(ssh.UnknownChannelType, "only session")
			continue
		}
		go s.handleSession(newChan)
	}
}

func (s *fakeSSHServer) handleSession(newChan ssh.NewChannel) {
	ch, reqs, err := newChan.Accept()
	if err != nil {
		return
	}
	defer ch.Close()
	for req := range reqs {
		switch req.Type {
		case "exec":
			var execReq struct{ Command string }
			if err := ssh.Unmarshal(req.Payload, &execReq); err != nil {
				req.Reply(false, nil)
				continue
			}
			req.Reply(true, nil)
			out, code := s.runCommand(execReq.Command)
			_, _ = ch.Write(out)
			_, _ = ch.SendRequest("exit-status", false, ssh.Marshal(struct{ Code uint32 }{uint32(code)}))
			_ = ch.Close()
		default:
			req.Reply(false, nil)
		}
	}
}

func (s *fakeSSHServer) runCommand(cmd string) ([]byte, int) {
	s.mu.Lock()
	defer s.mu.Unlock()
	trimmed := strings.TrimSpace(cmd)
	switch {
	case trimmed == "echo hello":
		return []byte("hello\n"), 0
	case strings.HasPrefix(trimmed, "exit "):
		return nil, 1
	case strings.HasPrefix(trimmed, "id -u "):
		return []byte("1000\n"), 0
	case strings.Contains(trimmed, "pveum role list") || strings.Contains(trimmed, "pveum role add"):
		s.state["pve_role:"+extractField(trimmed, "add ", " ")] = "ok"
		return nil, 0
	case strings.Contains(trimmed, "pveum user list") || strings.Contains(trimmed, "pveum user add"):
		s.state["pve_user:orca@pam"] = "ok"
		return nil, 0
	case strings.Contains(trimmed, "pveum acl modify"):
		s.state["pve_acl"] = "ok"
		return nil, 0
	case strings.HasPrefix(trimmed, "mkdir -p ") && strings.Contains(trimmed, "authorized_keys"):
		return s.handleAuthKeyDeploy(trimmed)
	case strings.HasPrefix(trimmed, "cat > /etc/sudoers.d/"):
		return s.handleSudoersWrite(trimmed), 0
	case strings.HasPrefix(trimmed, "visudo -cf /etc/sudoers.d/orca"):
		force := s.forceSudoersInvalid
		if force || s.state["sudoers_valid"] != "true" {
			return []byte("/etc/sudoers.d/orca: syntax error\n"), 1
		}
		return []byte("/etc/sudoers.d/orca: parsed OK\n"), 0
	case strings.HasPrefix(trimmed, "cat /") && strings.HasSuffix(trimmed, "/authorized_keys"):
		return s.readAuthFile(trimmed[4:]), 0
	case strings.HasPrefix(trimmed, "cat /") && strings.Contains(trimmed, "/orca"):
		return s.readSudoers(trimmed[4:]), 0
	default:
		return []byte("sh: command not found\n"), 127
	}
}

func (s *fakeSSHServer) handleAuthKeyDeploy(cmd string) ([]byte, int) {
	parts := strings.Split(cmd, "'")
	var pubLine string
	if len(parts) >= 2 {
		pubLine = parts[1]
	}
	authPath := filepath.Join(s.authDir, "authorized_keys")
	existing := string(s.readFile(authPath))
	if !strings.Contains(existing, pubLine) {
		existing += pubLine + "\n"
	}
	if err := os.WriteFile(authPath, []byte(existing), 0o600); err != nil {
		return []byte("mkdir: permission denied\n"), 1
	}
	return nil, 0
}

func (s *fakeSSHServer) readAuthFile(path string) []byte {
	if strings.HasSuffix(path, "/authorized_keys") {
		return s.readFile(filepath.Join(s.authDir, "authorized_keys"))
	}
	return []byte("cat: " + path + ": No such file or directory\n")
}

func (s *fakeSSHServer) handleSudoersWrite(cmd string) []byte {
	idx := strings.Index(cmd, "\n")
	if idx < 0 {
		return []byte("sh: bad heredoc\n")
	}
	content := cmd[idx+1:]
	if end := strings.Index(content, "ORCA_SUDOERS_EOF"); end >= 0 {
		content = content[:end]
	}
	s.state["sudoers_content"] = content
	s.state["sudoers_valid"] = "true"
	return nil
}

func (s *fakeSSHServer) readSudoers(path string) []byte {
	if v, ok := s.state["sudoers_content"]; ok {
		return []byte(v)
	}
	return []byte("cat: " + path + ": No such file or directory\n")
}

func (s *fakeSSHServer) readFile(path string) []byte {
	b, _ := os.ReadFile(path)
	return b
}

func (s *fakeSSHServer) close() {
	s.listener.Close()
	<-s.done
}

func extractField(s, after, until string) string {
	i := strings.Index(s, after)
	if i < 0 {
		return ""
	}
	rest := s[i+len(after):]
	j := strings.Index(rest, until)
	if j < 0 {
		return rest
	}
	return rest[:j]
}

func fakeSSHClient(t *testing.T, srv *fakeSSHServer) *ssh.Client {
	t.Helper()
	config := &ssh.ClientConfig{
		User:            "root",
		Auth:            []ssh.AuthMethod{ssh.Password("pw")},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
		Timeout:         5 * time.Second,
	}
	client, err := ssh.Dial("tcp", srv.addr(), config)
	if err != nil {
		t.Fatalf("ssh.Dial: %v", err)
	}
	return client
}

func withSessionRunner(t *testing.T, conn *ssh.Client) {
	t.Helper()
	orig := sessionRunner
	t.Cleanup(func() { sessionRunner = orig })
	sessionRunner = &sshSessionRunner{client: conn}
}

func TestRunRemote_Success(t *testing.T) {
	srv := newFakeSSHServer(t)
	defer srv.close()
	conn := fakeSSHClient(t, srv)
	defer conn.Close()
	withSessionRunner(t, conn)
	out, err := runRemote("echo hello")
	if err != nil {
		t.Fatalf("runRemote: %v", err)
	}
	if strings.TrimSpace(string(out)) != "hello" {
		t.Errorf("output = %q, want hello", strings.TrimSpace(string(out)))
	}
}

func TestRunRemote_Failure(t *testing.T) {
	srv := newFakeSSHServer(t)
	defer srv.close()
	conn := fakeSSHClient(t, srv)
	defer conn.Close()
	withSessionRunner(t, conn)
	_, err := runRemote("exit 7")
	if err == nil {
		t.Fatal("expected error for non-zero exit")
	}
	if !strings.Contains(err.Error(), "run") {
		t.Errorf("error should mention run, got: %v", err)
	}
}

func TestDeployPubKey_Success(t *testing.T) {
	srv := newFakeSSHServer(t)
	defer srv.close()
	conn := fakeSSHClient(t, srv)
	defer conn.Close()
	withSessionRunner(t, conn)

	if err := deployPubKey("orca", "ssh-ed25519 AAAA test@orca"); err != nil {
		t.Fatalf("deployPubKey: %v", err)
	}
	out := srv.readFile(filepath.Join(srv.authDir, "authorized_keys"))
	if !strings.Contains(string(out), "ssh-ed25519 AAAA test@orca") {
		t.Errorf("auth file does not contain the key: %s", out)
	}
}

func TestDeployPubKey_Idempotent(t *testing.T) {
	srv := newFakeSSHServer(t)
	defer srv.close()
	conn := fakeSSHClient(t, srv)
	defer conn.Close()
	withSessionRunner(t, conn)

	if err := deployPubKey("orca", "ssh-ed25519 AAAA test@orca"); err != nil {
		t.Fatalf("first deploy: %v", err)
	}
	if err := deployPubKey("orca", "ssh-ed25519 AAAA test@orca"); err != nil {
		t.Fatalf("second deploy: %v", err)
	}
	out := srv.readFile(filepath.Join(srv.authDir, "authorized_keys"))
	if cnt := strings.Count(string(out), "ssh-ed25519 AAAA test@orca"); cnt != 1 {
		t.Errorf("key count = %d, want 1 (idempotent)", cnt)
	}
}

func TestCreateLinuxUser_Success(t *testing.T) {
	srv := newFakeSSHServer(t)
	defer srv.close()
	conn := fakeSSHClient(t, srv)
	defer conn.Close()
	withSessionRunner(t, conn)
	if err := createLinuxUser("orca"); err != nil {
		t.Fatalf("createLinuxUser: %v", err)
	}
}

func TestCreatePVERole_Success(t *testing.T) {
	srv := newFakeSSHServer(t)
	defer srv.close()
	conn := fakeSSHClient(t, srv)
	defer conn.Close()
	withSessionRunner(t, conn)
	if err := createPVERole("OrcaOperator"); err != nil {
		t.Fatalf("createPVERole: %v", err)
	}
}

func TestCreatePVEUser_Success(t *testing.T) {
	srv := newFakeSSHServer(t)
	defer srv.close()
	conn := fakeSSHClient(t, srv)
	defer conn.Close()
	withSessionRunner(t, conn)
	if err := createPVEUser("orca"); err != nil {
		t.Fatalf("createPVEUser: %v", err)
	}
}

func TestAssignPVEACL_Success(t *testing.T) {
	srv := newFakeSSHServer(t)
	defer srv.close()
	conn := fakeSSHClient(t, srv)
	defer conn.Close()
	withSessionRunner(t, conn)
	if err := assignPVEACL("orca", "OrcaOperator"); err != nil {
		t.Fatalf("assignPVEACL: %v", err)
	}
}

func TestWriteSudoers_Success(t *testing.T) {
	srv := newFakeSSHServer(t)
	defer srv.close()
	conn := fakeSSHClient(t, srv)
	defer conn.Close()
	withSessionRunner(t, conn)

	if err := writeSudoers("orca"); err != nil {
		t.Fatalf("writeSudoers: %v", err)
	}
	if srv.state["sudoers_valid"] != "true" {
		t.Error("sudoers not marked valid")
	}
	if !strings.Contains(srv.state["sudoers_content"], "orca ALL=(root) NOPASSWD: NOEXEC: /usr/bin/pct") {
		t.Errorf("sudoers content missing pct: %s", srv.state["sudoers_content"])
	}
}

func TestValidateSudoers_ParsedOK(t *testing.T) {
	srv := newFakeSSHServer(t)
	defer srv.close()
	conn := fakeSSHClient(t, srv)
	defer conn.Close()
	withSessionRunner(t, conn)

	srv.state["sudoers_valid"] = "true"
	if err := validateSudoers(); err != nil {
		t.Errorf("validateSudoers: %v", err)
	}
}

func TestValidateSudoers_Failure(t *testing.T) {
	srv := newFakeSSHServer(t)
	defer srv.close()
	conn := fakeSSHClient(t, srv)
	defer conn.Close()
	withSessionRunner(t, conn)

	srv.state["sudoers_valid"] = "false"
	if err := validateSudoers(); err == nil {
		t.Error("expected error for invalid sudoers")
	}
}

type staticDialer struct {
	client *ssh.Client
}

func (d *staticDialer) DialContext(ctx context.Context, network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) {
	return d.client, nil
}

type funcDialer struct {
	fn func(ctx context.Context, network, addr string, config *ssh.ClientConfig) (*ssh.Client, error)
}

func (d *funcDialer) DialContext(ctx context.Context, network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) {
	return d.fn(ctx, network, addr, config)
}

func TestBootstrapProxmox_FullFlow_Success(t *testing.T) {
	srv := newFakeSSHServer(t)
	defer srv.close()

	home := t.TempDir()
	t.Setenv("ORCA_HOME", home)
	if err := os.WriteFile(filepath.Join(home, "known_hosts"), []byte{}, 0o600); err != nil {
		t.Fatalf("create known_hosts: %v", err)
	}

	orig := sshDialer
	defer func() { sshDialer = orig }()
	origRunner := sessionRunner
	defer func() { sessionRunner = origRunner }()
	sessionRunner = nil
	sshDialer = &staticDialer{client: fakeSSHClient(t, srv)}

	host, _, _ := net.SplitHostPort(srv.addr())

	var logBuf bytes.Buffer
	result, err := BootstrapProxmox(t.Context(), Options{
		Host:       host,
		SSHKeyPath: certpaths.SSHKeyPath(),
		Logger:     slog.New(slog.NewTextHandler(&logBuf, nil)),
	})
	if err != nil {
		t.Fatalf("BootstrapProxmox: %v", err)
	}
	if result == nil {
		t.Fatal("result is nil")
	}
	if result.NodeName != host {
		t.Errorf("NodeName = %q, want %q", result.NodeName, host)
	}
	if result.NodeAddress != host+":8443" {
		t.Errorf("NodeAddress = %q, want %q:8443", result.NodeAddress, host)
	}
	if !strings.Contains(logBuf.String(), "proxmox.bootstrap_ok") {
		t.Errorf("expected bootstrap_ok log, got: %s", logBuf.String())
	}
}

func TestBootstrapProxmox_FullFlow_DeployPubKeyFails(t *testing.T) {
	srv := newFakeSSHServer(t)
	defer srv.close()

	home := t.TempDir()
	t.Setenv("ORCA_HOME", home)
	if err := os.WriteFile(filepath.Join(home, "known_hosts"), []byte{}, 0o600); err != nil {
		t.Fatalf("create known_hosts: %v", err)
	}

	orig := sshDialer
	defer func() { sshDialer = orig }()
	origRunner := sessionRunner
	defer func() { sessionRunner = origRunner }()
	sessionRunner = nil

	// Use a real client that connects to a server which will reject deploy
	// by returning a non-zero exit for the mkdir command. We achieve this
	// by using a dialer that returns a client to a server whose authDir
	// is read-only — but simpler: just use a fresh server that errors on
	// authorized_keys commands via a custom server. We reuse newFakeSSHServer
	// but sabotage it by pointing authDir to a read-only location.
	conn := fakeSSHClient(t, srv)
	defer conn.Close()
	sshDialer = &staticDialer{client: conn}

	host, _, _ := net.SplitHostPort(srv.addr())

	// Make authDir unwritable so deployPubKey's mkdir handler fails.
	srv.authDir = "/proc/1/forbidden-orca-test"

	_, err := BootstrapProxmox(t.Context(), Options{
		Host:       host,
		SSHKeyPath: certpaths.SSHKeyPath(),
	})
	if err == nil {
		t.Fatal("expected error from deployPubKey failure")
	}
	if !strings.Contains(err.Error(), "deploy pubkey") {
		t.Errorf("error should mention deploy pubkey, got: %v", err)
	}
}
