// Package infra gère l'infrastructure (serveurs, containers, nginx).
package infra

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"os"
	"strings"
	"sync"
	"time"

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

// SSHClient représente une connexion SSH à un serveur.
type SSHClient struct {
	server *Server
	client *ssh.Client
	mu     sync.Mutex
}

// SSHPool gère un pool de connexions SSH.
type SSHPool struct {
	clients map[int64]*SSHClient
	mu      sync.RWMutex
	timeout time.Duration
}

// SSHResult représente le résultat d'une commande SSH.
type SSHResult struct {
	Stdout   string
	Stderr   string
	ExitCode int
	Duration time.Duration
}

// NewSSHPool crée un nouveau pool SSH.
func NewSSHPool(timeout time.Duration) *SSHPool {
	if timeout == 0 {
		timeout = 30 * time.Second
	}
	return &SSHPool{
		clients: make(map[int64]*SSHClient),
		timeout: timeout,
	}
}

// Connect établit une connexion SSH à un serveur.
func (p *SSHPool) Connect(server *Server) (*SSHClient, error) {
	p.mu.Lock()
	defer p.mu.Unlock()

	// Vérifier si déjà connecté
	if client, ok := p.clients[server.ID]; ok {
		if client.isAlive() {
			return client, nil
		}
		// Connexion morte, la supprimer
		client.Close()
		delete(p.clients, server.ID)
	}

	// Lire la clé SSH
	keyData, err := os.ReadFile(server.SSHKeyFile)
	if err != nil {
		return nil, fmt.Errorf("read ssh key: %w", err)
	}

	signer, err := ssh.ParsePrivateKey(keyData)
	if err != nil {
		return nil, fmt.Errorf("parse ssh key: %w", err)
	}

	// Config SSH
	config := &ssh.ClientConfig{
		User: server.SSHUser,
		Auth: []ssh.AuthMethod{
			ssh.PublicKeys(signer),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(), // TODO: gérer les known_hosts
		Timeout:         p.timeout,
	}

	// Connexion
	addr := fmt.Sprintf("%s:%d", server.Host, server.SSHPort)
	sshClient, err := ssh.Dial("tcp", addr, config)
	if err != nil {
		return nil, fmt.Errorf("ssh dial %s: %w", addr, err)
	}

	client := &SSHClient{
		server: server,
		client: sshClient,
	}
	p.clients[server.ID] = client

	return client, nil
}

// Get récupère un client existant ou nil.
func (p *SSHPool) Get(serverID int64) *SSHClient {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return p.clients[serverID]
}

// Disconnect ferme la connexion à un serveur.
func (p *SSHPool) Disconnect(serverID int64) {
	p.mu.Lock()
	defer p.mu.Unlock()

	if client, ok := p.clients[serverID]; ok {
		client.Close()
		delete(p.clients, serverID)
	}
}

// CloseAll ferme toutes les connexions.
func (p *SSHPool) CloseAll() {
	p.mu.Lock()
	defer p.mu.Unlock()

	for id, client := range p.clients {
		client.Close()
		delete(p.clients, id)
	}
}

// isAlive vérifie si la connexion est active.
func (c *SSHClient) isAlive() bool {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.client == nil {
		return false
	}

	// Test rapide avec une session
	session, err := c.client.NewSession()
	if err != nil {
		return false
	}
	session.Close()
	return true
}

// Close ferme la connexion.
func (c *SSHClient) Close() error {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.client != nil {
		err := c.client.Close()
		c.client = nil
		return err
	}
	return nil
}

// Exec exécute une commande sur le serveur distant.
func (c *SSHClient) Exec(ctx context.Context, cmd string) (*SSHResult, error) {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.client == nil {
		return nil, fmt.Errorf("ssh client not connected")
	}

	session, err := c.client.NewSession()
	if err != nil {
		return nil, fmt.Errorf("create session: %w", err)
	}
	defer session.Close()

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

	start := time.Now()

	// Exécuter avec timeout
	done := make(chan error, 1)
	go func() {
		done <- session.Run(cmd)
	}()

	select {
	case <-ctx.Done():
		session.Signal(ssh.SIGKILL)
		return nil, ctx.Err()
	case err := <-done:
		result := &SSHResult{
			Stdout:   stdout.String(),
			Stderr:   stderr.String(),
			Duration: time.Since(start),
		}

		if err != nil {
			if exitErr, ok := err.(*ssh.ExitError); ok {
				result.ExitCode = exitErr.ExitStatus()
			} else {
				return nil, fmt.Errorf("exec command: %w", err)
			}
		}

		return result, nil
	}
}

// ExecSimple exécute une commande et retourne stdout.
func (c *SSHClient) ExecSimple(ctx context.Context, cmd string) (string, error) {
	result, err := c.Exec(ctx, cmd)
	if err != nil {
		return "", err
	}
	if result.ExitCode != 0 {
		return "", fmt.Errorf("command failed (exit %d): %s", result.ExitCode, result.Stderr)
	}
	return strings.TrimSpace(result.Stdout), nil
}

// WriteFile écrit un fichier sur le serveur distant via cat.
func (c *SSHClient) WriteFile(ctx context.Context, path string, content []byte, mode string) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.client == nil {
		return fmt.Errorf("ssh client not connected")
	}

	session, err := c.client.NewSession()
	if err != nil {
		return fmt.Errorf("create session: %w", err)
	}
	defer session.Close()

	// Utiliser cat pour écrire le fichier
	stdin, err := session.StdinPipe()
	if err != nil {
		return fmt.Errorf("stdin pipe: %w", err)
	}

	cmd := fmt.Sprintf("cat > %s && chmod %s %s", path, mode, path)
	if err := session.Start(cmd); err != nil {
		return fmt.Errorf("start command: %w", err)
	}

	if _, err := stdin.Write(content); err != nil {
		return fmt.Errorf("write content: %w", err)
	}
	stdin.Close()

	if err := session.Wait(); err != nil {
		return fmt.Errorf("wait command: %w", err)
	}

	return nil
}

// ReadFile lit un fichier depuis le serveur distant.
func (c *SSHClient) ReadFile(ctx context.Context, path string) ([]byte, error) {
	result, err := c.Exec(ctx, fmt.Sprintf("cat %s", path))
	if err != nil {
		return nil, err
	}
	if result.ExitCode != 0 {
		return nil, fmt.Errorf("read file failed: %s", result.Stderr)
	}
	return []byte(result.Stdout), nil
}

// FileExists vérifie si un fichier existe.
func (c *SSHClient) FileExists(ctx context.Context, path string) (bool, error) {
	result, err := c.Exec(ctx, fmt.Sprintf("test -e %s && echo yes || echo no", path))
	if err != nil {
		return false, err
	}
	return strings.TrimSpace(result.Stdout) == "yes", nil
}

// SFTP retourne un client SFTP pour des opérations de fichiers avancées.
// Note: nécessite github.com/pkg/sftp si on veut un vrai SFTP.
// Pour l'instant on utilise des commandes shell.

// CopyFile copie un fichier local vers le serveur distant.
func (c *SSHClient) CopyFile(ctx context.Context, localPath, remotePath string) error {
	content, err := os.ReadFile(localPath)
	if err != nil {
		return fmt.Errorf("read local file: %w", err)
	}
	return c.WriteFile(ctx, remotePath, content, "644")
}

// CopyFrom copie un fichier du serveur distant vers local.
func (c *SSHClient) CopyFrom(ctx context.Context, remotePath, localPath string) error {
	content, err := c.ReadFile(ctx, remotePath)
	if err != nil {
		return err
	}
	return os.WriteFile(localPath, content, 0644)
}

// StreamExec exécute une commande et stream la sortie.
func (c *SSHClient) StreamExec(ctx context.Context, cmd string, stdout, stderr io.Writer) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.client == nil {
		return fmt.Errorf("ssh client not connected")
	}

	session, err := c.client.NewSession()
	if err != nil {
		return fmt.Errorf("create session: %w", err)
	}
	defer session.Close()

	session.Stdout = stdout
	session.Stderr = stderr

	done := make(chan error, 1)
	go func() {
		done <- session.Run(cmd)
	}()

	select {
	case <-ctx.Done():
		session.Signal(ssh.SIGKILL)
		return ctx.Err()
	case err := <-done:
		return err
	}
}
