package capture

import (
	"bufio"
	"fmt"
	"io"
	"strings"
	"sync"

	"telephony-inspector/internal/sip"
	internalSSH "telephony-inspector/internal/ssh"
)

// Capturer handles SIP packet capture via SSH
type Capturer struct {
	sshClient *internalSSH.Client
	cleanup   func() error
	running   bool
	mu        sync.Mutex

	// Callbacks
	OnPacket func(*sip.Packet)
	OnError  func(error)
}

// NewCapturer creates a new capturer with the given SSH config
func NewCapturer(cfg internalSSH.Config) *Capturer {
	return &Capturer{
		sshClient: internalSSH.NewClient(cfg),
	}
}

// Connect establishes the SSH connection
func (c *Capturer) Connect() error {
	return c.sshClient.Connect()
}

// Close closes the connection and stops capture
func (c *Capturer) Close() error {
	c.Stop()
	return c.sshClient.Close()
}

// Start begins capturing SIP traffic
func (c *Capturer) Start(iface string, port int) error {
	c.mu.Lock()
	if c.running {
		c.mu.Unlock()
		return fmt.Errorf("capture already running")
	}
	c.running = true
	c.mu.Unlock()

	// Build tcpdump command
	// -l: line buffered for real-time output
	// -A: print packet payload in ASCII
	// -s 0: capture full packets
	// -nn: don't resolve hostnames or port names
	cmd := fmt.Sprintf("sudo tcpdump -l -nn -A -s 0 -i %s port %d 2>/dev/null", iface, port)

	stdout, stderr, cleanup, err := c.sshClient.StartCommand(cmd)
	if err != nil {
		c.mu.Lock()
		c.running = false
		c.mu.Unlock()
		return err
	}
	c.cleanup = cleanup

	// Process stdout in goroutine
	go c.processStream(stdout)

	// Log stderr
	go c.processErrors(stderr)

	return nil
}

// Stop stops the capture
func (c *Capturer) Stop() {
	c.mu.Lock()
	defer c.mu.Unlock()

	if !c.running {
		return
	}
	c.running = false

	if c.cleanup != nil {
		c.cleanup()
		c.cleanup = nil
	}
}

// IsRunning returns whether capture is active
func (c *Capturer) IsRunning() bool {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.running
}

func (c *Capturer) processStream(r io.Reader) {
	scanner := bufio.NewScanner(r)
	var buffer strings.Builder
	inSIPMessage := false

	for scanner.Scan() {
		c.mu.Lock()
		running := c.running
		c.mu.Unlock()
		if !running {
			break
		}

		line := scanner.Text()

		// Detect start of SIP message
		if isSIPStart(line) {
			// If we were building a message, parse it
			if buffer.Len() > 0 {
				c.parseAndEmit(buffer.String())
				buffer.Reset()
			}
			inSIPMessage = true
		}

		if inSIPMessage {
			buffer.WriteString(line)
			buffer.WriteString("\r\n")

			// Detect end of SIP message (double CRLF or content complete)
			// This is simplified - real implementation would track Content-Length
		}
	}

	// Parse remaining buffer
	if buffer.Len() > 0 {
		c.parseAndEmit(buffer.String())
	}
}

func (c *Capturer) processErrors(r io.Reader) {
	scanner := bufio.NewScanner(r)
	for scanner.Scan() {
		if c.OnError != nil {
			c.OnError(fmt.Errorf("tcpdump: %s", scanner.Text()))
		}
	}
}

func (c *Capturer) parseAndEmit(raw string) {
	packet, err := sip.Parse(raw)
	if err != nil {
		if c.OnError != nil {
			c.OnError(err)
		}
		return
	}
	if packet != nil && c.OnPacket != nil {
		c.OnPacket(packet)
	}
}

// isSIPStart checks if a line looks like the start of a SIP message
func isSIPStart(line string) bool {
	sipMethods := []string{"INVITE", "ACK", "BYE", "CANCEL", "REGISTER", "OPTIONS", "PRACK", "SUBSCRIBE", "NOTIFY", "PUBLISH", "INFO", "REFER", "MESSAGE", "UPDATE"}

	// Response
	if strings.HasPrefix(line, "SIP/2.0") {
		return true
	}

	// Request
	for _, m := range sipMethods {
		if strings.HasPrefix(line, m+" ") {
			return true
		}
	}

	return false
}
