package capture

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

	"telephony-inspector/internal/logger"
	"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
	currentNetInfo *NetInfo

	// 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...)

	logger.Info("Starting local capture: tcpdump %v", 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)
	}

	logger.Info("Local capture started successfully")

	// 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
	}
	logger.Info("Stopping local capture")
	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)
	// Increase buffer size to handle large packets (default is 64KB)
	const maxCapacity = 5 * 1024 * 1024 // 5MB
	buf := make([]byte, 0, 64*1024)
	scanner.Buffer(buf, maxCapacity)

	var buffer strings.Builder
	inSIPMessage := false
	var msgNetInfo *NetInfo
	contentLength := -1

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

		line := scanner.Text()

		// Check for tcpdump header
		if netInfo := parseTcpdumpHeader(line); netInfo != nil {
			c.currentNetInfo = netInfo
			continue
		}

		// Detect start of SIP message
		if idx := findSIPStart(line); idx != -1 {
			logger.Debug("SIP Start detected: %s", line)

			// Clean the line (remove prefix garbage)
			line = line[idx:]

			// If we were building a message, parse it with its OWN net info (which was latched previously)
			// Note: This edge case (buffer > 0 but new start) means previous message ended implicitly.
			// But wait, the msgNetInfo we just latched is for the NEW message.
			// The OLD message should have already been emitted or we are in a weird state.
			// Use the PREVIOUS msgNetInfo for the existing buffer if any.
			// Actually, single buffer logic is simple: emit what we have.
			if buffer.Len() > 0 {
				// We need to pass the net info that belongs to the buffered content.
				// But we just overwrote msgNetInfo.
				// Realistically, we should emit before latching new info.
				// But tcpdump header comes BEFORE the message.
				// So c.currentNetInfo is already the NEW info.
				// And the buffer contains the OLD message.
				// So when we started the OLD message, we latched OLD info.
				// We should persist that OLD info until emit.
				// This implies we need `pendingNetInfo` vs `currentNetInfo`.

				// Simplified approach: msgNetInfo stores the info for the message currently being built in buffer.
				// When we start a NEW message, the buffer contains the PREVIOUS message.
				// So we emit the buffer with the OLD msgNetInfo.
				// THEN we start the new message and update msgNetInfo to the NEW c.currentNetInfo.

				c.parseAndEmit(buffer.String(), msgNetInfo)
				buffer.Reset()
			}

			// NOW update msgNetInfo for the new message
			if c.currentNetInfo != nil {
				info := *c.currentNetInfo
				msgNetInfo = &info
			} else {
				msgNetInfo = nil
			}

			inSIPMessage = true
			contentLength = -1 // Reset for new message
		}

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

			// Check for Content-Length
			lowerLine := strings.ToLower(line)
			if strings.HasPrefix(lowerLine, "content-length:") || strings.HasPrefix(lowerLine, "l:") {
				parts := strings.Split(line, ":")
				if len(parts) >= 2 {
					val := strings.TrimSpace(parts[1])
					if val != "" {
						if cl, err := strconv.Atoi(val); err == nil {
							contentLength = cl
						}
					}
				}
			}

			// Check for end of headers (empty line)
			if line == "" {
				// If Content-Length is 0 (or not found, treating as 0 for safety in this context usually 0 for BYE/ACK)
				// Flush immediately
				if contentLength <= 0 {
					c.parseAndEmit(buffer.String(), msgNetInfo)
					buffer.Reset()
					inSIPMessage = false
					contentLength = -1
				}
			}
		}
	}

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

	if err := scanner.Err(); err != nil {
		logger.Error("Scanner error: %v", err)
		if c.OnError != nil {
			c.OnError(fmt.Errorf("scanner error: %w", err))
		}
	}
}

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") {
			logger.Info("tcpdump: %s", text)
			continue
		}
		logger.Error("tcpdump stderr: %s", text)
		if c.OnError != nil {
			c.OnError(fmt.Errorf("tcpdump: %s", text))
		}
	}
}

func (c *LocalCapturer) parseAndEmit(raw string, netInfo *NetInfo) {
	packet, err := sip.Parse(raw)
	if err != nil {
		// Suppress verbose error logging for partial packets unless debug
		// logger.Error("Error parsing SIP packet: %v", err)
		if c.OnError != nil {
			c.OnError(err)
		}
		return
	}
	if packet != nil {
		// Attach network info if available
		if netInfo != nil {
			packet.Timestamp = netInfo.Timestamp
			packet.SourceIP = netInfo.SourceIP
			packet.SourcePort = netInfo.SourcePort
			packet.DestIP = netInfo.DestIP
			packet.DestPort = netInfo.DestPort
		}
		logger.Debug("Packet parsed: %s %s -> %s", packet.Method, packet.SourceIP, packet.DestIP)
		if c.OnPacket != nil {
			c.OnPacket(packet)
		}
	}
}
