package capture

import (
	"fmt"
	"os"

	"telephony-inspector/internal/logger"
	"telephony-inspector/internal/sip"

	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
)

// PcapReader reads and parses pcap files
type PcapReader struct {
	path    string
	handle  *pcap.Handle
	packets []*sip.Packet
}

// NewPcapReader creates a new pcap reader
func NewPcapReader(path string) *PcapReader {
	logger.Info("PcapReader: Creating reader for %s", path)
	return &PcapReader{
		path:    path,
		packets: make([]*sip.Packet, 0),
	}
}

// ReadAll reads all SIP packets from the pcap file
func (r *PcapReader) ReadAll() ([]*sip.Packet, error) {
	logger.Info("PcapReader: Opening file %s", r.path)

	// Check if file exists
	if _, err := os.Stat(r.path); os.IsNotExist(err) {
		logger.Error("PcapReader: File does not exist: %s", r.path)
		return nil, fmt.Errorf("file does not exist: %s", r.path)
	}

	handle, err := pcap.OpenOffline(r.path)
	if err != nil {
		logger.Error("PcapReader: Failed to open pcap: %v", err)
		return nil, fmt.Errorf("failed to open pcap: %w", err)
	}
	defer handle.Close()

	logger.Info("PcapReader: File opened successfully, link type: %v", handle.LinkType())

	// Try setting BPF filter (optional)
	if err := handle.SetBPFFilter("port 5060 or port 5061"); err != nil {
		logger.Warn("PcapReader: Could not set BPF filter: %v (continuing without filter)", err)
	} else {
		logger.Debug("PcapReader: BPF filter set for SIP ports")
	}

	packetSource := gopacket.NewPacketSource(handle, handle.LinkType())

	totalPackets := 0
	sipPackets := 0

	for packet := range packetSource.Packets() {
		totalPackets++
		logger.Debug("PcapReader: Processing packet %d, layers: %v", totalPackets, packet.Layers())

		sipPacket := r.extractSIPPacket(packet, totalPackets)
		if sipPacket != nil {
			sipPackets++
			r.packets = append(r.packets, sipPacket)
			logger.Info("PcapReader: Found SIP packet %d: %s %s", sipPackets,
				func() string {
					if sipPacket.IsRequest {
						return string(sipPacket.Method)
					} else {
						return fmt.Sprintf("%d", sipPacket.StatusCode)
					}
				}(),
				sipPacket.CallID)
		}
	}

	logger.Info("PcapReader: Finished reading. Total packets: %d, SIP packets: %d", totalPackets, sipPackets)
	return r.packets, nil
}

// extractSIPPacket extracts SIP data from a gopacket
func (r *PcapReader) extractSIPPacket(packet gopacket.Packet, packetNum int) *sip.Packet {
	// Get network layer for IPs
	var srcIP, dstIP string
	var srcPort, dstPort int
	var payload []byte

	if ipLayer := packet.Layer(layers.LayerTypeIPv4); ipLayer != nil {
		ip := ipLayer.(*layers.IPv4)
		srcIP = ip.SrcIP.String()
		dstIP = ip.DstIP.String()
		logger.Debug("PcapReader: Packet %d IPv4 %s -> %s", packetNum, srcIP, dstIP)
	} else if ipLayer := packet.Layer(layers.LayerTypeIPv6); ipLayer != nil {
		ip := ipLayer.(*layers.IPv6)
		srcIP = ip.SrcIP.String()
		dstIP = ip.DstIP.String()
		logger.Debug("PcapReader: Packet %d IPv6 %s -> %s", packetNum, srcIP, dstIP)
	} else {
		logger.Debug("PcapReader: Packet %d has no IP layer", packetNum)
	}

	// Get transport layer for ports AND payload
	if udpLayer := packet.Layer(layers.LayerTypeUDP); udpLayer != nil {
		udp := udpLayer.(*layers.UDP)
		srcPort = int(udp.SrcPort)
		dstPort = int(udp.DstPort)
		payload = udp.Payload
		logger.Debug("PcapReader: Packet %d UDP %d -> %d, payload len: %d", packetNum, srcPort, dstPort, len(payload))
	} else if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
		tcp := tcpLayer.(*layers.TCP)
		srcPort = int(tcp.SrcPort)
		dstPort = int(tcp.DstPort)
		payload = tcp.Payload
		logger.Debug("PcapReader: Packet %d TCP %d -> %d, payload len: %d", packetNum, srcPort, dstPort, len(payload))
	} else {
		logger.Debug("PcapReader: Packet %d has no TCP/UDP layer", packetNum)
		return nil
	}

	if len(payload) == 0 {
		logger.Debug("PcapReader: Packet %d has empty payload", packetNum)
		return nil
	}

	payloadStr := string(payload)

	// Log first 200 chars to see full SIP headers (only on trace/debug if needed)
	/*
		preview := payloadStr
		if len(preview) > 200 {
			preview = preview[:200]
		}
		logger.Debug("PcapReader: Packet %d full payload preview: %q", packetNum, preview)
	*/

	// Check if it looks like SIP
	if !isSIPPayload(payloadStr) {
		// logger.Debug("PcapReader: Packet %d is not SIP", packetNum)
		return nil
	}

	logger.Debug("PcapReader: Packet %d detected as SIP, parsing...", packetNum)

	// Parse the SIP message
	sipPacket, err := sip.Parse(payloadStr)
	if err != nil {
		logger.Warn("PcapReader: Packet %d SIP parse error: %v", packetNum, err)
		return nil
	}
	if sipPacket == nil {
		logger.Warn("PcapReader: Packet %d SIP parse returned nil", packetNum)
		return nil
	}

	// Set network info
	sipPacket.SourceIP = srcIP
	sipPacket.SourcePort = srcPort
	sipPacket.DestIP = dstIP
	sipPacket.DestPort = dstPort
	sipPacket.Timestamp = packet.Metadata().Timestamp

	return sipPacket
}

// isSIPPayload checks if payload looks like a SIP message
func isSIPPayload(payload string) bool {
	if len(payload) < 4 {
		return false
	}

	// Check for SIP request methods
	methods := []string{"INVITE ", "ACK ", "BYE ", "CANCEL ", "REGISTER ", "OPTIONS ",
		"PRACK ", "SUBSCRIBE ", "NOTIFY ", "PUBLISH ", "INFO ", "REFER ", "MESSAGE ", "UPDATE "}
	for _, m := range methods {
		if len(payload) >= len(m) && payload[:len(m)] == m {
			logger.Debug("isSIPPayload: Detected SIP method: %s", m[:len(m)-1])
			return true
		}
	}

	// Check for SIP response
	if len(payload) >= 7 && payload[:7] == "SIP/2.0" {
		logger.Debug("isSIPPayload: Detected SIP response")
		return true
	}

	// Check if this looks like an SDP body (might be reassembled SIP)
	if len(payload) >= 4 && payload[:4] == "v=0\r" {
		logger.Debug("isSIPPayload: Detected SDP-only payload (no SIP headers)")
		// This is SDP without SIP headers - likely a reassembly issue
		// Log first 50 chars for debugging
		preview := payload
		if len(preview) > 50 {
			preview = preview[:50]
		}
		logger.Debug("isSIPPayload: SDP payload: %q", preview)
		return false
	}

	// Log what the payload starts with for debugging
	preview := payload
	if len(preview) > 20 {
		preview = preview[:20]
	}
	logger.Debug("isSIPPayload: Unrecognized payload start: %q", preview)

	return false
}

// GetPacketCount returns number of packets read
func (r *PcapReader) GetPacketCount() int {
	return len(r.packets)
}

// Close closes the reader
func (r *PcapReader) Close() error {
	if r.handle != nil {
		r.handle.Close()
	}
	return nil
}

// ListPcapFiles lists .pcap files in a directory
func ListPcapFiles(dir string) ([]string, error) {
	entries, err := os.ReadDir(dir)
	if err != nil {
		return nil, err
	}

	var files []string
	for _, e := range entries {
		if e.IsDir() {
			continue
		}
		name := e.Name()
		if len(name) > 5 && (name[len(name)-5:] == ".pcap" || name[len(name)-7:] == ".pcapng") {
			files = append(files, name)
		}
	}
	return files, nil
}
