package ws

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"os/exec"
	"sync"
	"syscall"
	"unsafe"

	"github.com/creack/pty"
)

type PTYSession struct {
	ID     string
	ptmx   *os.File
	cmd    *exec.Cmd
	done   chan struct{}
}

type PTYManager struct {
	sessions map[string]*PTYSession
	mu       sync.Mutex
	client   *Client
}

func NewPTYManager(c *Client) *PTYManager {
	return &PTYManager{
		sessions: make(map[string]*PTYSession),
		client:   c,
	}
}

// StartPTY startet eine neue PTY-Session mit /bin/sh
func (pm *PTYManager) StartPTY(sessionID string, cols, rows uint16) error {
	pm.mu.Lock()
	defer pm.mu.Unlock()

	if _, exists := pm.sessions[sessionID]; exists {
		return fmt.Errorf("PTY-Session %s existiert bereits", sessionID)
	}

	cmd := exec.Command("/bin/sh", "-l")
	cmd.Env = append(os.Environ(),
		"TERM=xterm-256color",
		fmt.Sprintf("COLUMNS=%d", cols),
		fmt.Sprintf("LINES=%d", rows),
	)

	ptmx, err := pty.StartWithSize(cmd, &pty.Winsize{Rows: rows, Cols: cols})
	if err != nil {
		return fmt.Errorf("PTY starten fehlgeschlagen: %w", err)
	}

	session := &PTYSession{
		ID:   sessionID,
		ptmx: ptmx,
		cmd:  cmd,
		done: make(chan struct{}),
	}

	pm.sessions[sessionID] = session

	// PTY-Ausgabe lesen und an Backend senden
	go pm.readPTYOutput(session)

	log.Printf("PTY-Session gestartet: %s (%dx%d)", sessionID, cols, rows)
	return nil
}

// StopPTY beendet eine PTY-Session
func (pm *PTYManager) StopPTY(sessionID string) {
	pm.mu.Lock()
	session, exists := pm.sessions[sessionID]
	if exists {
		delete(pm.sessions, sessionID)
	}
	pm.mu.Unlock()

	if !exists {
		return
	}

	close(session.done)
	session.cmd.Process.Kill()
	session.ptmx.Close()
	log.Printf("PTY-Session beendet: %s", sessionID)
}

// WriteToPTY schreibt Daten in die PTY-Stdin
func (pm *PTYManager) WriteToPTY(sessionID string, data []byte) error {
	pm.mu.Lock()
	session, exists := pm.sessions[sessionID]
	pm.mu.Unlock()

	if !exists {
		return fmt.Errorf("PTY-Session %s nicht gefunden", sessionID)
	}

	_, err := session.ptmx.Write(data)
	return err
}

// ResizePTY aendert die PTY-Groesse
func (pm *PTYManager) ResizePTY(sessionID string, cols, rows uint16) error {
	pm.mu.Lock()
	session, exists := pm.sessions[sessionID]
	pm.mu.Unlock()

	if !exists {
		return fmt.Errorf("PTY-Session %s nicht gefunden", sessionID)
	}

	ws := &struct {
		Rows, Cols, X, Y uint16
	}{Rows: rows, Cols: cols}

	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL,
		session.ptmx.Fd(),
		syscall.TIOCSWINSZ,
		uintptr(unsafe.Pointer(ws)),
	)
	if errno != 0 {
		return errno
	}
	return nil
}

func (pm *PTYManager) readPTYOutput(session *PTYSession) {
	buf := make([]byte, 4096)
	for {
		select {
		case <-session.done:
			return
		default:
		}

		n, err := session.ptmx.Read(buf)
		if err != nil {
			// PTY geschlossen — Session beenden und Backend informieren
			pm.StopPTY(session.ID)
			// Exit-Nachricht senden
			msg := map[string]interface{}{
				"type":       "pty_exit",
				"session_id": session.ID,
			}
			if data, err := json.Marshal(msg); err == nil {
				pm.client.SendMessage(Message{
					Type:  "event",
					Event: "pty_exit",
					Params: map[string]interface{}{
						"session_id": session.ID,
					},
				})
				_ = data
			}
			return
		}

		if n > 0 {
			// Daten als Binary-Message senden (session_id Prefix)
			if err := pm.client.messageHandler.SendSessionData(session.ID, buf[:n]); err != nil {
				log.Printf("PTY-Daten senden fehlgeschlagen: %v", err)
				return
			}
		}
	}
}
