package system

import (
	"encoding/json"
	"io"
	"net/http"
	"os"
	"os/exec"
	"os/user"
	"sync"
	"syscall"
	"time"

	"github.com/atlasos/calypso/internal/common/logger"
	"github.com/creack/pty"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
)

const (
	// WebSocket timeouts
	writeWait  = 10 * time.Second
	pongWait   = 60 * time.Second
	pingPeriod = (pongWait * 9) / 10
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  4096,
	WriteBufferSize: 4096,
	CheckOrigin: func(r *http.Request) bool {
		// Allow all origins - in production, validate against allowed domains
		return true
	},
}

// TerminalSession manages a single terminal session
type TerminalSession struct {
	conn     *websocket.Conn
	pty      *os.File
	cmd      *exec.Cmd
	logger   *logger.Logger
	mu       sync.RWMutex
	closed   bool
	username string
	done     chan struct{}
}

// HandleTerminalWebSocket handles WebSocket connection for terminal
func HandleTerminalWebSocket(c *gin.Context, log *logger.Logger) {
	// Verify authentication
	userID, exists := c.Get("user_id")
	if !exists {
		log.Warn("Terminal WebSocket: unauthorized access", "ip", c.ClientIP())
		c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
		return
	}

	username, _ := c.Get("username")
	if username == nil {
		username = userID
	}

	log.Info("Terminal WebSocket: connection attempt", "username", username, "ip", c.ClientIP())

	// Upgrade connection
	conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		log.Error("Terminal WebSocket: upgrade failed", "error", err)
		return
	}

	log.Info("Terminal WebSocket: connection upgraded", "username", username)

	// Create session
	session := &TerminalSession{
		conn:     conn,
		logger:   log,
		username: username.(string),
		done:     make(chan struct{}),
	}

	// Start terminal
	if err := session.startPTY(); err != nil {
		log.Error("Terminal WebSocket: failed to start PTY", "error", err, "username", username)
		session.sendError(err.Error())
		session.close()
		return
	}

	// Handle messages and PTY output
	go session.handleRead()
	go session.handleWrite()
}

// startPTY starts the PTY session
func (s *TerminalSession) startPTY() error {
	// Get user info
	currentUser, err := user.Lookup(s.username)
	if err != nil {
		// Fallback to current user
		currentUser, err = user.Current()
		if err != nil {
			return err
		}
	}

	// Determine shell
	shell := os.Getenv("SHELL")
	if shell == "" {
		shell = "/bin/bash"
	}

	// Create command
	s.cmd = exec.Command(shell)
	s.cmd.Env = append(os.Environ(),
		"TERM=xterm-256color",
		"HOME="+currentUser.HomeDir,
		"USER="+currentUser.Username,
		"USERNAME="+currentUser.Username,
	)
	s.cmd.Dir = currentUser.HomeDir

	// Start PTY
	ptyFile, err := pty.Start(s.cmd)
	if err != nil {
		return err
	}

	s.pty = ptyFile

	// Set initial size
	pty.Setsize(ptyFile, &pty.Winsize{
		Rows: 24,
		Cols: 80,
	})

	return nil
}

// handleRead handles incoming WebSocket messages
func (s *TerminalSession) handleRead() {
	defer s.close()

	// Set read deadline and pong handler
	s.conn.SetReadDeadline(time.Now().Add(pongWait))
	s.conn.SetPongHandler(func(string) error {
		s.conn.SetReadDeadline(time.Now().Add(pongWait))
		return nil
	})

	for {
		select {
		case <-s.done:
			return
		default:
			messageType, data, err := s.conn.ReadMessage()
			if err != nil {
				if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
					s.logger.Error("Terminal WebSocket: read error", "error", err)
				}
				return
			}

			// Handle binary messages (raw input)
			if messageType == websocket.BinaryMessage {
				s.writeToPTY(data)
				continue
			}

			// Handle text messages (JSON commands)
			if messageType == websocket.TextMessage {
				var msg map[string]interface{}
				if err := json.Unmarshal(data, &msg); err != nil {
					continue
				}

				switch msg["type"] {
				case "input":
					if data, ok := msg["data"].(string); ok {
						s.writeToPTY([]byte(data))
					}

				case "resize":
					if cols, ok1 := msg["cols"].(float64); ok1 {
						if rows, ok2 := msg["rows"].(float64); ok2 {
							s.resizePTY(uint16(cols), uint16(rows))
						}
					}

				case "ping":
					s.writeWS(websocket.TextMessage, []byte(`{"type":"pong"}`))
				}
			}
		}
	}
}

// handleWrite handles PTY output to WebSocket
func (s *TerminalSession) handleWrite() {
	defer s.close()

	ticker := time.NewTicker(pingPeriod)
	defer ticker.Stop()

	// Read from PTY and write to WebSocket
	buffer := make([]byte, 4096)
	for {
		select {
		case <-s.done:
			return
		case <-ticker.C:
			// Send ping
			if err := s.writeWS(websocket.PingMessage, nil); err != nil {
				return
			}
		default:
			// Read from PTY
			if s.pty != nil {
				n, err := s.pty.Read(buffer)
				if err != nil {
					if err != io.EOF {
						s.logger.Error("Terminal WebSocket: PTY read error", "error", err)
					}
					return
				}
				if n > 0 {
					// Write binary data to WebSocket
					if err := s.writeWS(websocket.BinaryMessage, buffer[:n]); err != nil {
						return
					}
				}
			}
		}
	}
}

// writeToPTY writes data to PTY
func (s *TerminalSession) writeToPTY(data []byte) {
	s.mu.RLock()
	closed := s.closed
	pty := s.pty
	s.mu.RUnlock()

	if closed || pty == nil {
		return
	}

	if _, err := pty.Write(data); err != nil {
		s.logger.Error("Terminal WebSocket: PTY write error", "error", err)
	}
}

// resizePTY resizes the PTY
func (s *TerminalSession) resizePTY(cols, rows uint16) {
	s.mu.RLock()
	closed := s.closed
	ptyFile := s.pty
	s.mu.RUnlock()

	if closed || ptyFile == nil {
		return
	}

	// Use pty.Setsize from package, not method from variable
	pty.Setsize(ptyFile, &pty.Winsize{
		Cols: cols,
		Rows: rows,
	})
}

// writeWS writes message to WebSocket
func (s *TerminalSession) writeWS(messageType int, data []byte) error {
	s.mu.RLock()
	closed := s.closed
	conn := s.conn
	s.mu.RUnlock()

	if closed || conn == nil {
		return io.ErrClosedPipe
	}

	conn.SetWriteDeadline(time.Now().Add(writeWait))
	return conn.WriteMessage(messageType, data)
}

// sendError sends error message
func (s *TerminalSession) sendError(errMsg string) {
	msg := map[string]interface{}{
		"type":  "error",
		"error": errMsg,
	}
	data, _ := json.Marshal(msg)
	s.writeWS(websocket.TextMessage, data)
}

// close closes the terminal session
func (s *TerminalSession) close() {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.closed {
		return
	}

	s.closed = true
	close(s.done)

	// Close PTY
	if s.pty != nil {
		s.pty.Close()
	}

	// Kill process
	if s.cmd != nil && s.cmd.Process != nil {
		s.cmd.Process.Signal(syscall.SIGTERM)
		time.Sleep(100 * time.Millisecond)
		if s.cmd.ProcessState == nil || !s.cmd.ProcessState.Exited() {
			s.cmd.Process.Kill()
		}
	}

	// Close WebSocket
	if s.conn != nil {
		s.conn.Close()
	}

	s.logger.Info("Terminal WebSocket: session closed", "username", s.username)
}
