package ws

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"net"
	"sync"
	"time"
)

// TunnelSession repraesentiert eine einzelne TCP-Verbindung zu einem Ziel
type TunnelSession struct {
	ID         string
	TunnelID   string
	TargetHost string
	TargetPort int
	Conn       net.Conn
	Active     bool
}

// TunnelManager verwaltet alle Sessions auf Agent-Seite
type TunnelManager struct {
	client   *Client
	sessions map[string]*TunnelSession
	mutex    sync.RWMutex
}

func NewTunnelManager(client *Client) *TunnelManager {
	return &TunnelManager{
		client:   client,
		sessions: make(map[string]*TunnelSession),
	}
}

// ConnectSession oeffnet eine neue TCP-Verbindung fuer eine Session
func (tm *TunnelManager) ConnectSession(sessionID, tunnelID, targetHost string, targetPort int) error {
	target := fmt.Sprintf("%s:%d", targetHost, targetPort)
	conn, err := net.DialTimeout("tcp", target, 10*time.Second)
	if err != nil {
		return fmt.Errorf("Verbindung zu %s fehlgeschlagen: %w", target, err)
	}

	session := &TunnelSession{
		ID:         sessionID,
		TunnelID:   tunnelID,
		TargetHost: targetHost,
		TargetPort: targetPort,
		Conn:       conn,
		Active:     true,
	}

	tm.mutex.Lock()
	tm.sessions[sessionID] = session
	tm.mutex.Unlock()

	// Ziel -> Backend Forwarding starten
	go tm.forwardFromTarget(session)

	log.Printf("Session %s: Verbindung zu %s hergestellt", sessionID, target)
	return nil
}

// DisconnectSession schliesst eine Session
func (tm *TunnelManager) DisconnectSession(sessionID string) {
	tm.mutex.Lock()
	session, exists := tm.sessions[sessionID]
	if !exists {
		tm.mutex.Unlock()
		return
	}

	session.Active = false
	delete(tm.sessions, sessionID)
	tm.mutex.Unlock()

	if session.Conn != nil {
		session.Conn.Close()
	}

	log.Printf("Session %s: geschlossen", sessionID)
}

// SendDataToSession schreibt Daten in die Ziel-Verbindung einer Session
func (tm *TunnelManager) SendDataToSession(sessionID string, data []byte) error {
	tm.mutex.RLock()
	session, exists := tm.sessions[sessionID]
	tm.mutex.RUnlock()

	if !exists || !session.Active {
		return fmt.Errorf("Session %s nicht aktiv", sessionID)
	}

	_, err := session.Conn.Write(data)
	if err != nil {
		tm.DisconnectSession(sessionID)
		return fmt.Errorf("Session %s: Write fehlgeschlagen: %w", sessionID, err)
	}

	return nil
}

// forwardFromTarget liest Daten vom Ziel und schickt sie als Binary-Message ans Backend
func (tm *TunnelManager) forwardFromTarget(session *TunnelSession) {
	defer func() {
		if session.Active {
			tm.DisconnectSession(session.ID)
		}
		log.Printf("Session %s: Forwarding beendet", session.ID)
	}()

	buffer := make([]byte, 32768)

	for session.Active {
		session.Conn.SetReadDeadline(time.Now().Add(60 * time.Second))

		n, err := session.Conn.Read(buffer)
		if err != nil {
			if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
				continue
			}
			if err == io.EOF {
				log.Printf("Session %s: Ziel-Verbindung geschlossen", session.ID)
			} else if session.Active {
				log.Printf("Session %s: Read-Fehler: %v", session.ID, err)
			}
			break
		}

		if n > 0 {
			if err := tm.client.messageHandler.SendSessionData(session.ID, buffer[:n]); err != nil {
				log.Printf("Session %s: WebSocket senden fehlgeschlagen: %v", session.ID, err)
				break
			}
		}
	}
}

// ProcessBinaryMessage verarbeitet eingehende Binary-Messages vom Backend (Proxy -> Ziel)
func (tm *TunnelManager) ProcessBinaryMessage(data []byte) error {
	sessionID, payload, err := ParseBinaryMessage(data)
	if err != nil {
		return fmt.Errorf("Binary-Message parsen: %w", err)
	}

	return tm.SendDataToSession(sessionID, payload)
}

// GetActiveSessions gibt die Anzahl aktiver Sessions zurueck
func (tm *TunnelManager) GetActiveSessions() int {
	tm.mutex.RLock()
	defer tm.mutex.RUnlock()
	return len(tm.sessions)
}

func (tm *TunnelManager) generateID() string {
	bytes := make([]byte, 8)
	rand.Read(bytes)
	return hex.EncodeToString(bytes)
}

// ParseBinaryMessage parst das Binary-Format: [id_len:1][id][data]
func ParseBinaryMessage(data []byte) (id string, payload []byte, err error) {
	if len(data) < 1 {
		return "", nil, fmt.Errorf("Daten zu kurz")
	}

	idLen := int(data[0])
	if len(data) < 1+idLen {
		return "", nil, fmt.Errorf("ID zu kurz")
	}

	id = string(data[1 : 1+idLen])
	payload = data[1+idLen:]
	return id, payload, nil
}
