package capture

import (
	"bufio"
	"context"
	"fmt"
	"io"
	"os/exec"
	"strings"
	"sync"

	"telephony-inspector/internal/sip"
)

// LocalCapturer handles SIP packet capture locally via tcpdump
type LocalCapturer struct {
	cmd     *exec.Cmd
	cancel  context.CancelFunc
	running bool
	mu      sync.Mutex

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

// NewLocalCapturer creates a new local capturer
func NewLocalCapturer() *LocalCapturer {
	return &LocalCapturer{}
}

// Start begins capturing SIP traffic locally
func (c *LocalCapturer) 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()

	ctx, cancel := context.WithCancel(context.Background())
	c.cancel = cancel

	// Build tcpdump command
	// -l: line buffered
	// -A: print packet payload in ASCII
	// -s 0: capture full packets
	// -nn: don't resolve hostnames or port names
	args := []string{"-l", "-nn", "-A", "-s", "0", "-i", iface, "port", fmt.Sprintf("%d", port)}
	c.cmd = exec.CommandContext(ctx, "tcpdump", args...)

	stdout, err := c.cmd.StdoutPipe()
	if err != nil {
		c.mu.Lock()
		c.running = false
		c.mu.Unlock()
		return fmt.Errorf("failed to get stdout: %w", err)
	}

	stderr, err := c.cmd.StderrPipe()
	if err != nil {
		c.mu.Lock()
		c.running = false
		c.mu.Unlock()
		return fmt.Errorf("failed to get stderr: %w", err)
	}

	if err := c.cmd.Start(); err != nil {
		c.mu.Lock()
		c.running = false
		c.mu.Unlock()
		return fmt.Errorf("failed to start tcpdump: %w", err)
	}

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

	// Log stderr
	go c.processErrors(stderr)

	return nil
}

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

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

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

	if c.cmd != nil && c.cmd.Process != nil {
		c.cmd.Process.Kill()
		c.cmd.Wait()
	}
}

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

// Close cleans up resources
func (c *LocalCapturer) Close() error {
	c.Stop()
	return nil
}

func (c *LocalCapturer) 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")
		}
	}

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

func (c *LocalCapturer) processErrors(r io.Reader) {
	scanner := bufio.NewScanner(r)
	for scanner.Scan() {
		text := scanner.Text()
		// tcpdump prints "listening on..." to stderr, ignore it
		if strings.Contains(text, "listening on") {
			continue
		}
		if c.OnError != nil {
			c.OnError(fmt.Errorf("tcpdump: %s", text))
		}
	}
}

func (c *LocalCapturer) 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)
	}
}
