package sip

import (
	"regexp"
	"strings"
	"time"
)

// Method represents a SIP method
type Method string

const (
	MethodINVITE    Method = "INVITE"
	MethodACK       Method = "ACK"
	MethodBYE       Method = "BYE"
	MethodCANCEL    Method = "CANCEL"
	MethodREGISTER  Method = "REGISTER"
	MethodOPTIONS   Method = "OPTIONS"
	MethodPRACK     Method = "PRACK"
	MethodSUBSCRIBE Method = "SUBSCRIBE"
	MethodNOTIFY    Method = "NOTIFY"
	MethodPUBLISH   Method = "PUBLISH"
	MethodINFO      Method = "INFO"
	MethodREFER     Method = "REFER"
	MethodMESSAGE   Method = "MESSAGE"
	MethodUPDATE    Method = "UPDATE"
)

// Packet represents a parsed SIP packet
type Packet struct {
	Timestamp  time.Time
	Raw        string
	IsRequest  bool
	Method     Method
	StatusCode int
	StatusText string
	RequestURI string

	// Common headers
	CallID      string
	From        string
	To          string
	Via         []string
	CSeq        string
	Contact     string
	ContentType string

	// Network info
	SourceIP   string
	SourcePort int
	DestIP     string
	DestPort   int

	// SDP body if present
	SDP *SDP
}

// SDP represents a parsed SDP body
type SDP struct {
	Raw           string
	SessionName   string
	Origin        string
	Connection    string // c= line (IP for media)
	MediaSections []MediaSection
}

// MediaSection represents an m= line in SDP
type MediaSection struct {
	MediaType  string // audio, video, etc
	Port       int
	Protocol   string // RTP/AVP, etc
	Formats    []string
	Attributes map[string]string
}

// Parse parses a raw SIP message
func Parse(raw string) (*Packet, error) {
	p := &Packet{Raw: raw}

	lines := strings.Split(raw, "\r\n")
	if len(lines) == 0 {
		lines = strings.Split(raw, "\n")
	}
	if len(lines) == 0 {
		return nil, nil
	}

	// Parse first line (request or response)
	firstLine := lines[0]
	if strings.HasPrefix(firstLine, "SIP/2.0") {
		p.IsRequest = false
		parts := strings.SplitN(firstLine, " ", 3)
		if len(parts) >= 2 {
			p.StatusCode = parseStatusCode(parts[1])
		}
		if len(parts) >= 3 {
			p.StatusText = parts[2]
		}
	} else {
		p.IsRequest = true
		parts := strings.SplitN(firstLine, " ", 3)
		if len(parts) >= 1 {
			p.Method = Method(parts[0])
		}
		if len(parts) >= 2 {
			p.RequestURI = parts[1]
		}
	}

	// Parse headers
	headerEnd := 0
	for i, line := range lines[1:] {
		if line == "" {
			headerEnd = i + 2
			break
		}
		p.parseHeader(line)
	}

	// Parse SDP body if present
	if headerEnd > 0 && headerEnd < len(lines) && strings.Contains(p.ContentType, "application/sdp") {
		body := strings.Join(lines[headerEnd:], "\r\n")
		p.SDP = ParseSDP(body)
	}

	return p, nil
}

func (p *Packet) parseHeader(line string) {
	idx := strings.Index(line, ":")
	if idx < 0 {
		return
	}
	name := strings.TrimSpace(line[:idx])
	value := strings.TrimSpace(line[idx+1:])

	switch strings.ToLower(name) {
	case "call-id", "i":
		p.CallID = value
	case "from", "f":
		p.From = value
	case "to", "t":
		p.To = value
	case "via", "v":
		p.Via = append(p.Via, value)
	case "cseq":
		p.CSeq = value
	case "contact", "m":
		p.Contact = value
	case "content-type", "c":
		p.ContentType = value
	}
}

func parseStatusCode(s string) int {
	var code int
	for _, c := range s {
		if c >= '0' && c <= '9' {
			code = code*10 + int(c-'0')
		} else {
			break
		}
	}
	return code
}

// ParseSDP parses an SDP body
func ParseSDP(body string) *SDP {
	sdp := &SDP{Raw: body}
	lines := strings.Split(body, "\r\n")
	if len(lines) <= 1 {
		lines = strings.Split(body, "\n")
	}

	var currentMedia *MediaSection
	for _, line := range lines {
		if len(line) < 2 || line[1] != '=' {
			continue
		}
		typ := line[0]
		value := line[2:]

		switch typ {
		case 'o':
			sdp.Origin = value
		case 's':
			sdp.SessionName = value
		case 'c':
			sdp.Connection = value
		case 'm':
			if currentMedia != nil {
				sdp.MediaSections = append(sdp.MediaSections, *currentMedia)
			}
			currentMedia = parseMediaLine(value)
		case 'a':
			if currentMedia != nil {
				parseAttribute(currentMedia, value)
			}
		}
	}
	if currentMedia != nil {
		sdp.MediaSections = append(sdp.MediaSections, *currentMedia)
	}

	return sdp
}

func parseMediaLine(value string) *MediaSection {
	parts := strings.Fields(value)
	if len(parts) < 3 {
		return &MediaSection{}
	}
	m := &MediaSection{
		MediaType:  parts[0],
		Protocol:   parts[2],
		Attributes: make(map[string]string),
	}
	if port := parsePort(parts[1]); port > 0 {
		m.Port = port
	}
	if len(parts) > 3 {
		m.Formats = parts[3:]
	}
	return m
}

func parsePort(s string) int {
	var port int
	for _, c := range s {
		if c >= '0' && c <= '9' {
			port = port*10 + int(c-'0')
		} else {
			break
		}
	}
	return port
}

func parseAttribute(m *MediaSection, value string) {
	if idx := strings.Index(value, ":"); idx > 0 {
		m.Attributes[value[:idx]] = value[idx+1:]
	} else {
		m.Attributes[value] = ""
	}
}

// Regular expressions for extracting IP addresses
var ipRegex = regexp.MustCompile(`\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}`)

// GetSDPMediaIP extracts the media IP from SDP
func (s *SDP) GetSDPMediaIP() string {
	if s.Connection != "" {
		matches := ipRegex.FindString(s.Connection)
		if matches != "" {
			return matches
		}
	}
	return ""
}
