package rdp

import (
	"fmt"
	"sync"
	"time"

	"github.com/google/uuid"
)

// RDPSession represents an active RDP connection with its associated state.
type RDPSession struct {
	ID        string    `json:"id"`
	Config    RDPConfig `json:"config"`
	Backend   RDPBackend `json:"-"`
	Buffer    *PixelBuffer `json:"-"`
	ConnID    int64     `json:"connectionId"`
	Connected time.Time `json:"connected"`
}

// RDPService manages multiple RDP sessions. It uses a backend factory to
// create new RDPBackend instances — during development the factory returns
// MockBackend instances; in production it will return FreeRDP-backed ones.
type RDPService struct {
	sessions       map[string]*RDPSession
	mu             sync.RWMutex
	backendFactory func() RDPBackend
}

// NewRDPService creates a new service with the given backend factory.
// The factory is called once per Connect to create a fresh backend.
func NewRDPService(factory func() RDPBackend) *RDPService {
	return &RDPService{
		sessions:       make(map[string]*RDPSession),
		backendFactory: factory,
	}
}

// Connect creates a new RDP session using the provided configuration.
// Returns the session ID on success.
func (s *RDPService) Connect(config RDPConfig, connectionID int64) (string, error) {
	// Apply defaults
	if config.Port <= 0 {
		config.Port = 3389
	}
	if config.Width <= 0 {
		config.Width = 1920
	}
	if config.Height <= 0 {
		config.Height = 1080
	}
	if config.ColorDepth <= 0 {
		config.ColorDepth = 32
	}
	if config.Security == "" {
		config.Security = "nla"
	}

	backend := s.backendFactory()
	if err := backend.Connect(config); err != nil {
		return "", fmt.Errorf("rdp connect to %s:%d: %w", config.Hostname, config.Port, err)
	}

	sessionID := uuid.NewString()
	session := &RDPSession{
		ID:        sessionID,
		Config:    config,
		Backend:   backend,
		Buffer:    NewPixelBuffer(config.Width, config.Height),
		ConnID:    connectionID,
		Connected: time.Now(),
	}

	s.mu.Lock()
	s.sessions[sessionID] = session
	s.mu.Unlock()

	return sessionID, nil
}

// Disconnect tears down the RDP session and removes it from tracking.
func (s *RDPService) Disconnect(sessionID string) error {
	s.mu.Lock()
	session, ok := s.sessions[sessionID]
	if !ok {
		s.mu.Unlock()
		return fmt.Errorf("session %s not found", sessionID)
	}
	delete(s.sessions, sessionID)
	s.mu.Unlock()

	return session.Backend.Disconnect()
}

// SendMouse forwards a mouse event to the session's backend.
func (s *RDPService) SendMouse(sessionID string, x, y int, flags uint32) error {
	session, err := s.getSession(sessionID)
	if err != nil {
		return err
	}
	return session.Backend.SendMouseEvent(x, y, flags)
}

// SendKey forwards a key event to the session's backend.
func (s *RDPService) SendKey(sessionID string, scancode uint32, pressed bool) error {
	session, err := s.getSession(sessionID)
	if err != nil {
		return err
	}
	return session.Backend.SendKeyEvent(scancode, pressed)
}

// SendClipboard forwards clipboard text to the session's backend.
func (s *RDPService) SendClipboard(sessionID string, data string) error {
	session, err := s.getSession(sessionID)
	if err != nil {
		return err
	}
	return session.Backend.SendClipboard(data)
}

// GetFrame returns the current RGBA pixel buffer for the given session.
func (s *RDPService) GetFrame(sessionID string) ([]byte, error) {
	session, err := s.getSession(sessionID)
	if err != nil {
		return nil, err
	}
	return session.Backend.GetFrame()
}

// GetSessionInfo returns the session metadata without the backend or buffer.
// This is safe to serialize to JSON for the frontend.
func (s *RDPService) GetSessionInfo(sessionID string) (*RDPSession, error) {
	session, err := s.getSession(sessionID)
	if err != nil {
		return nil, err
	}
	return session, nil
}

// ListSessions returns all active RDP sessions.
func (s *RDPService) ListSessions() []*RDPSession {
	s.mu.RLock()
	defer s.mu.RUnlock()

	list := make([]*RDPSession, 0, len(s.sessions))
	for _, sess := range s.sessions {
		list = append(list, sess)
	}
	return list
}

// IsConnected returns whether a specific session is still connected.
func (s *RDPService) IsConnected(sessionID string) bool {
	session, err := s.getSession(sessionID)
	if err != nil {
		return false
	}
	return session.Backend.IsConnected()
}

// getSession is an internal helper that retrieves a session by ID.
func (s *RDPService) getSession(sessionID string) (*RDPSession, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	session, ok := s.sessions[sessionID]
	if !ok {
		return nil, fmt.Errorf("session %s not found", sessionID)
	}
	return session, nil
}
