// SIP тестер для e2e проверки pulse-lets-go + FreeSWITCH.
// Два режима:
//
//	uas  — отвечает 200 OK на SIP INVITE, симулирует PBX-ноду
//	uac  — шлёт SIP INVITE в FreeSWITCH, симулирует оператора
//
// Примеры:
//
//	./siptest -mode uas                (PBX-нода на порту 5090)
//	./siptest -mode uac -r 10 -l 100  (оператор, 10 CPS, до 100 одновременных)
package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/pulse-lets-go/internal/util"
)

const (
	uasPort = 5090 // порт PBX-ноды (принимает bridged вызовы)
	uacPort = 5061 // порт оператора (шлёт INVITE в FS)
	fsPort  = 5080 // порт FreeSWITCH (external profile — для транков)
)

// Статистика
var (
	callsSent   atomic.Int64
	callsOK     atomic.Int64
	callsFailed atomic.Int64
)

func main() {
	mode := flag.String("mode", "uas", "uas | uac")
	rate := flag.Int("r", 10, "CPS (для uac)")
	limit := flag.Int("l", 100, "максимум одновременных (для uac)")
	maxCalls := flag.Int("m", 1000, "всего вызовов (для uac)")
	host := flag.String("host", "127.0.0.1", "FS host/IP")
	dest := flag.String("dest", "88005553535", "dest номер")
	caller := flag.String("caller", "74951234567", "caller_id")
	flag.Parse()

	log.Printf("[siptest] режим=%s", *mode)

	switch *mode {
	case "uas":
		runUAS()
	case "uac":
		runUAC(*rate, *limit, *maxCalls, *dest, *caller, *host)
	default:
		log.Fatalf("неизвестный режим: %s", *mode)
	}
}

// ── UAS: PBX-нода (отвечает 200 OK на INVITE) ────────────────

func runUAS() {
	addr := fmt.Sprintf("127.0.0.1:%d", uasPort)
	conn, err := net.ListenPacket("udp", addr)
	if err != nil {
		log.Fatalf("UAS listen %s: %v", addr, err)
	}
	defer conn.Close()

	log.Printf("[uas] PBX-нода слушает %s", addr)
	buf := make([]byte, 4096)

	for {
		n, remoteAddr, err := conn.ReadFrom(buf)
		if err != nil {
			log.Printf("[uas] ошибка чтения: %v", err)
			continue
		}
		msg := string(buf[:n])

		if strings.HasPrefix(msg, "INVITE") || strings.Contains(msg, "INVITE") {
			response := buildSIPResponse(msg, addr)
			time.Sleep(50 * time.Millisecond) // имитация обработки вызова
			if _, err := conn.WriteTo([]byte(response), remoteAddr); err != nil {
				log.Printf("[uas] ошибка ответа: %v", err)
			}
			log.Printf("[uas] INVITE отвечен: %v", remoteAddr)
		}

		// ACK и BYE игнорируем
		if strings.Contains(msg, "ACK") || strings.Contains(msg, "BYE") {
			continue
		}
	}
}

func buildSIPResponse(invite, localAddr string) string {
	via := extractHeader(invite, "Via:")
	from := extractHeader(invite, "From:")
	to := extractHeader(invite, "To:")
	callID := extractHeader(invite, "Call-ID:")
	cseq := extractHeader(invite, "CSeq:")

	// Добавляем tag к To если его нет
	to = strings.TrimSpace(to)
	if !strings.Contains(to, ";tag=") {
		to += ";tag=uas-" + util.RandomHex(4)
	}

	return fmt.Sprintf("SIP/2.0 200 OK\r\n"+
		"%s\r\n"+
		"%s\r\n"+
		"%s\r\n"+
		"%s\r\n"+
		"%s\r\n"+
		"Contact: <sip:pbx@%s>\r\n"+
		"Content-Length: 0\r\n"+
		"\r\n",
		via, from, to, callID, cseq, localAddr)
}

func extractHeader(msg, prefix string) string {
	lines := strings.Split(msg, "\n")
	for _, line := range lines {
		line = strings.TrimSpace(line)
		line = strings.TrimRight(line, "\r")
		if strings.HasPrefix(line, prefix) {
			return line
		}
	}
	return prefix
}

// ── UAC: Оператор (шлёт INVITE в FS) ─────────────────────────

func runUAC(rate, limit, maxCalls int, dest, caller, host string) {
	fsAddr := fmt.Sprintf("%s:%d", host, fsPort)
	srcAddr := fmt.Sprintf("127.0.0.1:%d", uacPort)

	localAddr, err := net.ResolveUDPAddr("udp", srcAddr)
	if err != nil {
		log.Fatalf("resolve %s: %v", srcAddr, err)
	}
	remoteAddr, err := net.ResolveUDPAddr("udp", fsAddr)
	if err != nil {
		log.Fatalf("resolve %s: %v", fsAddr, err)
	}

	conn, err := net.ListenUDP("udp", localAddr)
	if err != nil {
		log.Fatalf("listen %s: %v", srcAddr, err)
	}
	defer conn.Close()

	log.Printf("[uac] оператор %s → FS %s (rate=%d/s, limit=%d, total=%d)", srcAddr, fsAddr, rate, limit, maxCalls)

	sem := make(chan struct{}, limit) // ограничение одновременных
	var wg sync.WaitGroup
	ticker := time.NewTicker(time.Second / time.Duration(rate))
	defer ticker.Stop()

	// Читаем ответы от FS
	go func() {
		buf := make([]byte, 4096)
		for {
			n, _, err := conn.ReadFrom(buf)
			if err != nil {
				return
			}
			msg := string(buf[:n])
			if strings.Contains(msg, "200 OK") {
				callsOK.Add(1)
			}
		}
	}()

	startTime := time.Now()
	callNum := 0

	for callNum < maxCalls {
		select {
		case <-ticker.C:
			sem <- struct{}{} // занимаем слот
			wg.Add(1)
			callNum++

			go func(num int) {
				defer wg.Done()
				defer func() { <-sem }() // освобождаем слот

				invite := buildSIPInvite(dest, caller, num, srcAddr, fsAddr)

				_, err := conn.WriteTo([]byte(invite), remoteAddr)
				callsSent.Add(1)
				if err != nil {
					callsFailed.Add(1)
					return
				}
			}(callNum)
		}
	}

	wg.Wait()

	elapsed := time.Since(startTime)
	totalCalls := callsSent.Load()
	okCalls := callsOK.Load()

	log.Printf("[uac] ================ ИТОГИ ================")
	log.Printf("  Всего вызовов:     %d", totalCalls)
	log.Printf("  Успешно (200 OK):  %d", okCalls)
	log.Printf("  CPS (факт):        %.1f", float64(totalCalls)/elapsed.Seconds())
	log.Printf("  Длительность:      %v", elapsed.Round(time.Millisecond))
}

func buildSIPInvite(dest, caller string, callNum int, srcAddr, fsAddr string) string {
	callID := fmt.Sprintf("call-%d-%s@%s", callNum, util.RandomHex(4), "127.0.0.1")
	branch := "z9hG4bK-" + util.RandomHex(8)

	return fmt.Sprintf("INVITE sip:%s@%s SIP/2.0\r\n"+
		"Via: SIP/2.0/UDP %s;branch=%s\r\n"+
		"From: <sip:%s@127.0.0.1>;tag=caller-%d\r\n"+
		"To: <sip:%s@127.0.0.1>\r\n"+
		"Call-ID: %s\r\n"+
		"CSeq: 1 INVITE\r\n"+
		"Contact: <sip:%s@%s>\r\n"+
		"Content-Length: 0\r\n"+
		"\r\n",
		dest, fsAddr, srcAddr, branch, caller, callNum, dest, callID, caller, srcAddr)
}
