//go:build !omit_whatsapp

package channels

import (
	"context"
	"encoding/json"
	"net"
	"testing"
	"time"

	"github.com/YspCoder/clawgo/pkg/bus"
	waProto "go.mau.fi/whatsmeow/proto/waE2E"
	"go.mau.fi/whatsmeow/types"
	"google.golang.org/protobuf/proto"
)

func TestParseWhatsAppBridgeListenAddr(t *testing.T) {
	tests := []struct {
		name    string
		input   string
		want    string
		wantErr bool
	}{
		{name: "raw host", input: "127.0.0.1:3001", want: "127.0.0.1:3001"},
		{name: "ws url", input: "ws://localhost:3001", want: "localhost:3001"},
		{name: "ws url path", input: "ws://localhost:3001/ws", want: "localhost:3001"},
		{name: "missing host", input: "ws:///ws", wantErr: true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := ParseWhatsAppBridgeListenAddr(tt.input)
			if tt.wantErr {
				if err == nil {
					t.Fatalf("expected error, got none")
				}
				return
			}
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if got != tt.want {
				t.Fatalf("got %q, want %q", got, tt.want)
			}
		})
	}
}

func TestBridgeStatusURL(t *testing.T) {
	got, err := BridgeStatusURL("ws://localhost:3001/ws")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if got != "http://localhost:3001/status" {
		t.Fatalf("got %q", got)
	}
}

func TestBridgeStatusURLWithNestedPath(t *testing.T) {
	got, err := BridgeStatusURL("ws://localhost:7788/whatsapp/ws")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if got != "http://localhost:7788/whatsapp/status" {
		t.Fatalf("got %q", got)
	}
}

func TestIsLocalRemoteAddr(t *testing.T) {
	ipv4Net := &net.IPNet{IP: net.ParseIP("192.168.1.10"), Mask: net.CIDRMask(24, 32)}
	ipv6Net := &net.IPNet{IP: net.ParseIP("fe80::1"), Mask: net.CIDRMask(64, 128)}

	tests := []struct {
		name       string
		remoteAddr string
		want       bool
	}{
		{name: "loopback", remoteAddr: "127.0.0.1:4321", want: true},
		{name: "local interface ipv4", remoteAddr: "192.168.1.10:4321", want: true},
		{name: "local interface ipv6", remoteAddr: "[fe80::1]:4321", want: true},
		{name: "non local ip", remoteAddr: "192.168.1.11:4321", want: false},
		{name: "invalid host", remoteAddr: "not-an-ip", want: false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := isLocalRemoteAddr(tt.remoteAddr, []net.Addr{ipv4Net, ipv6Net})
			if got != tt.want {
				t.Fatalf("got %v want %v", got, tt.want)
			}
		})
	}
}

func TestNormalizeWhatsAppRecipientJID(t *testing.T) {
	tests := []struct {
		input string
		want  string
	}{
		{input: "8613012345678", want: "8613012345678@s.whatsapp.net"},
		{input: "1203630-123456789@g.us", want: "1203630-123456789@g.us"},
		{input: "1203630-123456789", want: "1203630-123456789@g.us"},
	}
	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			got, err := normalizeWhatsAppRecipientJID(tt.input)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if got.String() != tt.want {
				t.Fatalf("got %q, want %q", got.String(), tt.want)
			}
		})
	}
}

func TestDetectWhatsAppMediaType(t *testing.T) {
	tests := []struct {
		path     string
		data     []byte
		wantKind string
		wantMime string
	}{
		{path: "photo.jpg", data: []byte{0xff, 0xd8, 0xff, 0xe0}, wantKind: "image", wantMime: "image/jpeg"},
		{path: "clip.mp4", data: []byte("...."), wantKind: "video", wantMime: "video/mp4"},
		{path: "voice.ogg", data: []byte("OggS"), wantKind: "audio", wantMime: "audio/ogg"},
		{path: "report.pdf", data: []byte("%PDF-1.4"), wantKind: "document", wantMime: "application/pdf"},
	}
	for _, tt := range tests {
		t.Run(tt.path, func(t *testing.T) {
			gotKind, gotMime := detectWhatsAppMediaType(tt.path, tt.data)
			if gotKind != tt.wantKind {
				t.Fatalf("kind got %q want %q", gotKind, tt.wantKind)
			}
			if gotMime != tt.wantMime {
				t.Fatalf("mime got %q want %q", gotMime, tt.wantMime)
			}
		})
	}
}

func TestWhatsAppSendIncludesMediaPayload(t *testing.T) {
	msg := bus.OutboundMessage{
		Channel:   "whatsapp",
		ChatID:    "12345@s.whatsapp.net",
		Content:   "hello",
		Media:     "/tmp/demo.png",
		ReplyToID: "wamid.demo",
	}
	payload := map[string]interface{}{
		"type":    "message",
		"to":      msg.ChatID,
		"content": msg.Content,
	}
	if msg.ReplyToID != "" {
		payload["reply_to_id"] = msg.ReplyToID
	}
	if msg.Media != "" {
		payload["media"] = []string{msg.Media}
	}
	data, err := json.Marshal(payload)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}
	var parsed map[string]interface{}
	if err := json.Unmarshal(data, &parsed); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}
	media, ok := parsed["media"].([]interface{})
	if !ok || len(media) != 1 || media[0] != msg.Media {
		t.Fatalf("unexpected media payload: %#v", parsed["media"])
	}
	if parsed["reply_to_id"] != msg.ReplyToID {
		t.Fatalf("unexpected reply_to_id payload: %#v", parsed["reply_to_id"])
	}
}

func TestExtractWhatsAppContextInfo(t *testing.T) {
	ctx := &waProto.ContextInfo{MentionedJID: []string{"8613012345678@s.whatsapp.net"}}
	msg := &waProto.Message{
		ExtendedTextMessage: &waProto.ExtendedTextMessage{
			Text:        proto.String("hi"),
			ContextInfo: ctx,
		},
	}
	got := extractWhatsAppContextInfo(msg)
	if got == nil || len(got.GetMentionedJID()) != 1 {
		t.Fatalf("expected context info to be extracted")
	}
}

func TestNormalizeComparableJID(t *testing.T) {
	jid := types.NewJID("8613012345678", types.DefaultUserServer)
	got := normalizeComparableJID(jid.ADString())
	if got != jid.String() {
		t.Fatalf("got %q want %q", got, jid.String())
	}
}

func TestApplyWhatsAppReplyContext(t *testing.T) {
	msg := &waProto.Message{Conversation: proto.String("hello")}
	applyWhatsAppReplyContext(msg, types.NewJID("12345", types.DefaultUserServer), "wamid.reply", "")
	if msg.GetExtendedTextMessage() == nil || msg.GetExtendedTextMessage().GetContextInfo().GetStanzaID() != "wamid.reply" {
		t.Fatalf("expected reply context on text message")
	}
}

func TestMarkIncomingReadReceiptUsesSenderOnlyForGroups(t *testing.T) {
	service := &WhatsAppBridgeService{}
	done := make(chan struct{}, 2)
	var gotChat, gotSender types.JID
	service.markReadFn = func(ctx context.Context, ids []types.MessageID, timestamp time.Time, chat, sender types.JID) error {
		gotChat = chat
		gotSender = sender
		done <- struct{}{}
		return nil
	}

	service.markIncomingReadReceipt(types.NewJID("1203630-123456789", types.GroupServer), types.NewJID("8613012345678", types.DefaultUserServer), types.MessageID("abc"), time.Now())
	select {
	case <-done:
	case <-time.After(500 * time.Millisecond):
		t.Fatalf("timed out waiting for group mark read")
	}
	if gotChat.Server != types.GroupServer || gotSender.Server != types.DefaultUserServer {
		t.Fatalf("unexpected group mark read args: chat=%s sender=%s", gotChat, gotSender)
	}

	service.markIncomingReadReceipt(types.NewJID("8613012345678", types.DefaultUserServer), types.NewJID("8620000000000", types.DefaultUserServer), types.MessageID("def"), time.Now())
	select {
	case <-done:
	case <-time.After(500 * time.Millisecond):
		t.Fatalf("timed out waiting for direct mark read")
	}
	if !gotSender.IsEmpty() {
		t.Fatalf("expected empty sender for direct chat, got %s", gotSender)
	}
}
