package main

import (
	"fmt"
	"log"
	"net"
	"strings"
	"sync"
	"time"
)

// LinkedServer represents a connection to another IRC server
type LinkedServer struct {
	name        string
	conn        net.Conn
	host        string
	port        int
	password    string
	hub         bool
	description string
	connected   bool
	lastPing    time.Time
	server      *Server
	mu          sync.RWMutex
}

// LinkConfig represents configuration for server linking
type LinkConfig struct {
	Enable      bool   `json:"enable"`
	ServerPort  int    `json:"server_port"`
	Password    string `json:"password"`
	Hub         bool   `json:"hub"`
	AutoConnect bool   `json:"auto_connect"`
	Links       []struct {
		Name        string `json:"name"`
		Host        string `json:"host"`
		Port        int    `json:"port"`
		Password    string `json:"password"`
		AutoConnect bool   `json:"auto_connect"`
		Hub         bool   `json:"hub"`
		Description string `json:"description"`
	} `json:"links"`
}

// ServerMessage represents a server-to-server message
type ServerMessage struct {
	Prefix  string
	Command string
	Params  []string
	Source  string
	Target  string
}

// NewLinkedServer creates a new linked server connection
func NewLinkedServer(name, host string, port int, password string, hub bool, description string, server *Server) *LinkedServer {
	return &LinkedServer{
		name:        name,
		host:        host,
		port:        port,
		password:    password,
		hub:         hub,
		description: description,
		server:      server,
		connected:   false,
		lastPing:    time.Now(),
	}
}

// Connect establishes a connection to the remote server
func (ls *LinkedServer) Connect() error {
	ls.mu.Lock()
	defer ls.mu.Unlock()

	if ls.connected {
		return fmt.Errorf("server %s is already connected", ls.name)
	}

	// Connect to remote server
	var addr string
	if strings.Contains(ls.host, ":") {
		// IPv6 address, enclose in brackets
		addr = fmt.Sprintf("[%s]:%d", ls.host, ls.port)
	} else {
		addr = fmt.Sprintf("%s:%d", ls.host, ls.port)
	}
	conn, err := net.DialTimeout("tcp", addr, 30*time.Second)
	if err != nil {
		return fmt.Errorf("failed to connect to %s: %v", ls.name, err)
	}

	ls.conn = conn
	ls.connected = true

	log.Printf("Connected to server %s at %s", ls.name, addr)

	// Start handling the connection
	go ls.Handle()

	// Send server introduction
	ls.SendServerAuth()

	return nil
}

// Disconnect closes the connection to the remote server
func (ls *LinkedServer) Disconnect() {
	ls.mu.Lock()
	defer ls.mu.Unlock()

	if !ls.connected {
		return
	}

	if ls.conn != nil {
		ls.conn.Close()
		ls.conn = nil
	}
	ls.connected = false

	log.Printf("Disconnected from server %s", ls.name)
}

// SendServerAuth sends authentication to the remote server
func (ls *LinkedServer) SendServerAuth() {
	// Send PASS command
	ls.SendMessage(fmt.Sprintf("PASS %s", ls.password))

	// Send SERVER command
	ls.SendMessage(fmt.Sprintf("SERVER %s 1 :%s", ls.server.config.Server.Name, ls.server.config.Server.Description))

	log.Printf("Sent authentication to server %s", ls.name)
}

// SendMessage sends a raw message to the linked server
func (ls *LinkedServer) SendMessage(message string) error {
	ls.mu.RLock()
	defer ls.mu.RUnlock()

	if !ls.connected || ls.conn == nil {
		return fmt.Errorf("server %s is not connected", ls.name)
	}

	_, err := fmt.Fprintf(ls.conn, "%s\r\n", message)
	if err != nil {
		log.Printf("Error sending message to server %s: %v", ls.name, err)
		ls.connected = false
	}

	return err
}

// Handle processes messages from the linked server
func (ls *LinkedServer) Handle() {
	defer func() {
		if r := recover(); r != nil {
			log.Printf("Panic in server link handler for %s: %v", ls.name, r)
		}
		ls.Disconnect()
	}()

	scanner := net.Conn(ls.conn)
	buffer := make([]byte, 4096)

	for ls.connected {
		// Set read deadline
		ls.conn.SetReadDeadline(time.Now().Add(5 * time.Minute))

		n, err := scanner.Read(buffer)
		if err != nil {
			if ls.connected {
				log.Printf("Read error from server %s: %v", ls.name, err)
			}
			break
		}

		data := string(buffer[:n])
		lines := strings.Split(strings.TrimSpace(data), "\n")

		for _, line := range lines {
			line = strings.TrimSpace(line)
			if line == "" {
				continue
			}

			ls.handleServerMessage(line)
		}
	}
}

// handleServerMessage processes a single message from the linked server
func (ls *LinkedServer) handleServerMessage(line string) {
	log.Printf("Received from server %s: %s", ls.name, line)

	parts := strings.Fields(line)
	if len(parts) == 0 {
		return
	}

	var command string
	var params []string

	// Parse message prefix
	if parts[0][0] == ':' {
		parts = parts[1:]
	}

	if len(parts) == 0 {
		return
	}

	command = strings.ToUpper(parts[0])
	params = parts[1:]

	// Handle server-to-server commands
	switch command {
	case "PASS":
		ls.handleServerPass(params)
	case "SERVER":
		ls.handleServerIntro(params)
	case "PING":
		ls.handleServerPing(params)
	case "PONG":
		ls.handleServerPong()
	case "SQUIT":
		ls.handleServerQuit(params)
	case "NICK":
		ls.handleRemoteNick(params)
	case "USER":
		ls.handleRemoteUser(params)
	case "JOIN":
		ls.handleRemoteJoin(params)
	case "PART":
		ls.handleRemotePart(params)
	case "QUIT":
		ls.handleRemoteQuit(params)
	case "PRIVMSG":
		ls.handleRemotePrivmsg(params)
	default:
		log.Printf("Unknown server command from %s: %s", ls.name, command)
	}
}

// handleServerPass handles PASS command from remote server
func (ls *LinkedServer) handleServerPass(params []string) {
	if len(params) < 1 {
		log.Printf("Invalid PASS from server %s", ls.name)
		ls.Disconnect()
		return
	}

	password := params[0]
	if password != ls.password {
		log.Printf("Invalid password from server %s", ls.name)
		ls.Disconnect()
		return
	}

	log.Printf("Server %s authenticated successfully", ls.name)
}

// handleServerIntro handles SERVER command from remote server
func (ls *LinkedServer) handleServerIntro(params []string) {
	if len(params) < 3 {
		log.Printf("Invalid SERVER from server %s", ls.name)
		ls.Disconnect()
		return
	}

	serverName := params[0]
	hopCount := params[1]
	description := strings.Join(params[2:], " ")

	if description[0] == ':' {
		description = description[1:]
	}

	log.Printf("Server introduced: %s (hops: %s) - %s", serverName, hopCount, description)

	// Send our user list and channel list to the remote server
	ls.sendBurst()
}

// handleServerPing handles PING from remote server
func (ls *LinkedServer) handleServerPing(params []string) {
	if len(params) < 1 {
		return
	}

	token := params[0]
	ls.SendMessage(fmt.Sprintf("PONG %s %s", ls.server.config.Server.Name, token))
}

// handleServerPong handles PONG from remote server
func (ls *LinkedServer) handleServerPong() {
	ls.mu.Lock()
	ls.lastPing = time.Now()
	ls.mu.Unlock()
}

// handleServerQuit handles SQUIT from remote server
func (ls *LinkedServer) handleServerQuit(params []string) {
	if len(params) >= 1 {
		reason := strings.Join(params, " ")
		log.Printf("Server %s quit: %s", ls.name, reason)
	}
	ls.Disconnect()
}

// sendBurst sends initial data to newly connected server
func (ls *LinkedServer) sendBurst() {
	// Send all local users
	for _, client := range ls.server.GetClients() {
		if client.IsRegistered() {
			ls.SendMessage(fmt.Sprintf("NICK %s 1 %d %s %s %s %s :%s",
				client.Nick(),
				time.Now().Unix(),
				client.User(),
				client.Host(),
				ls.server.config.Server.Name,
				client.Nick(),
				client.Realname()))
		}
	}

	// Send all channels and their members
	for _, channel := range ls.server.GetChannels() {
		// Send channel creation
		ls.SendMessage(fmt.Sprintf("NJOIN %s :", channel.name))

		// Send channel members
		var members []string
		for _, client := range channel.GetClients() {
			members = append(members, client.Nick())
		}

		if len(members) > 0 {
			ls.SendMessage(fmt.Sprintf("NJOIN %s :%s", channel.name, strings.Join(members, " ")))
		}

		// Send channel topic if exists
		if channel.topic != "" {
			ls.SendMessage(fmt.Sprintf("TOPIC %s :%s", channel.name, channel.topic))
		}
	}

	// End of burst
	ls.SendMessage("EOB")
	log.Printf("Sent burst to server %s", ls.name)
}

// Remote user handling functions
func (ls *LinkedServer) handleRemoteNick(params []string) {
	// Handle remote NICK command
	log.Printf("Remote NICK from %s: %s", ls.name, strings.Join(params, " "))
}

func (ls *LinkedServer) handleRemoteUser(params []string) {
	// Handle remote USER command
	log.Printf("Remote USER from %s: %s", ls.name, strings.Join(params, " "))
}

func (ls *LinkedServer) handleRemoteJoin(params []string) {
	// Handle remote JOIN command
	log.Printf("Remote JOIN from %s: %s", ls.name, strings.Join(params, " "))
}

func (ls *LinkedServer) handleRemotePart(params []string) {
	// Handle remote PART command
	log.Printf("Remote PART from %s: %s", ls.name, strings.Join(params, " "))
}

func (ls *LinkedServer) handleRemoteQuit(params []string) {
	// Handle remote QUIT command
	log.Printf("Remote QUIT from %s: %s", ls.name, strings.Join(params, " "))
}

func (ls *LinkedServer) handleRemotePrivmsg(params []string) {
	// Handle remote PRIVMSG command
	log.Printf("Remote PRIVMSG from %s: %s", ls.name, strings.Join(params, " "))
}

// IsConnected returns whether the server is currently connected
func (ls *LinkedServer) IsConnected() bool {
	ls.mu.RLock()
	defer ls.mu.RUnlock()
	return ls.connected
}

// Name returns the server name
func (ls *LinkedServer) Name() string {
	return ls.name
}

// Description returns the server description
func (ls *LinkedServer) Description() string {
	return ls.description
}
