package qq

import (
	"context"
	"errors"
	"strings"
	"testing"

	"github.com/jackc/pgx/v5"

	identitypkg "github.com/memohai/memoh/internal/channel/identities"
	routepkg "github.com/memohai/memoh/internal/channel/route"
)

const testQQOpenID = "00112233445566778899AABBCCDDEEFF"

func TestQQResolveTargetMapsRouteID(t *testing.T) {
	t.Parallel()

	adapter := NewQQAdapter(nil)
	adapter.SetRouteResolver(&fakeQQRouteResolver{
		byID: map[string]routepkg.Route{
			"3fe2bad9-3eae-4f23-872c-b7a63662aa00": {
				ID:          "3fe2bad9-3eae-4f23-872c-b7a63662aa00",
				Platform:    "qq",
				ReplyTarget: "c2c:" + testQQOpenID,
			},
		},
	})

	got, err := adapter.resolveTarget(context.Background(), "3fe2bad9-3eae-4f23-872c-b7a63662aa00")
	if err != nil {
		t.Fatalf("resolveTarget returned error: %v", err)
	}
	if got != "c2c:"+testQQOpenID {
		t.Fatalf("unexpected mapped target: %q", got)
	}
}

func TestQQResolveTargetMapsIdentityID(t *testing.T) {
	t.Parallel()

	adapter := NewQQAdapter(nil)
	adapter.SetChannelIdentityResolver(&fakeQQIdentityResolver{
		canonical: map[string][]identitypkg.ChannelIdentity{
			"3fe2bad9-3eae-4f23-872c-b7a63662aa00": {
				{ID: "qq-identity-1", Channel: "qq", ChannelSubjectID: testQQOpenID},
			},
		},
	})

	got, err := adapter.resolveTarget(context.Background(), "3fe2bad9-3eae-4f23-872c-b7a63662aa00")
	if err != nil {
		t.Fatalf("resolveTarget returned error: %v", err)
	}
	if got != "c2c:"+testQQOpenID {
		t.Fatalf("unexpected mapped target: %q", got)
	}
}

func TestQQResolveTargetMapsUserID(t *testing.T) {
	t.Parallel()

	adapter := NewQQAdapter(nil)
	adapter.SetChannelIdentityResolver(&fakeQQIdentityResolver{
		userScoped: map[string][]identitypkg.ChannelIdentity{
			"3fe2bad9-3eae-4f23-872c-b7a63662aa00": {
				{ID: "qq-identity-1", Channel: "qq", ChannelSubjectID: testQQOpenID},
			},
		},
	})

	got, err := adapter.resolveTarget(context.Background(), "3fe2bad9-3eae-4f23-872c-b7a63662aa00")
	if err != nil {
		t.Fatalf("resolveTarget returned error: %v", err)
	}
	if got != "c2c:"+testQQOpenID {
		t.Fatalf("unexpected mapped target: %q", got)
	}
}

func TestQQResolveTargetSkipsNonOpenIDQQIdentity(t *testing.T) {
	t.Parallel()

	adapter := NewQQAdapter(nil)
	adapter.SetChannelIdentityResolver(&fakeQQIdentityResolver{
		canonical: map[string][]identitypkg.ChannelIdentity{
			"3fe2bad9-3eae-4f23-872c-b7a63662aa00": {
				{ID: "qq-guild-identity-1", Channel: "qq", ChannelSubjectID: "guild-user-id"},
			},
		},
	})

	got, err := adapter.resolveTarget(context.Background(), "3fe2bad9-3eae-4f23-872c-b7a63662aa00")
	if err != nil {
		t.Fatalf("resolveTarget returned error: %v", err)
	}
	if got != "c2c:3fe2bad9-3eae-4f23-872c-b7a63662aa00" {
		t.Fatalf("unexpected mapped target: %q", got)
	}
}

func TestQQResolveTargetReturnsRouteResolverErrors(t *testing.T) {
	t.Parallel()

	adapter := NewQQAdapter(nil)
	adapter.SetRouteResolver(&fakeQQRouteResolver{err: errors.New("route store unavailable")})

	_, err := adapter.resolveTarget(context.Background(), "3fe2bad9-3eae-4f23-872c-b7a63662aa00")
	if err == nil {
		t.Fatal("expected route resolver error")
	}
	if !strings.Contains(err.Error(), "route store unavailable") {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestQQResolveTargetReturnsIdentityResolverErrors(t *testing.T) {
	t.Parallel()

	adapter := NewQQAdapter(nil)
	adapter.SetChannelIdentityResolver(&fakeQQIdentityResolver{canonicalErr: errors.New("identity store unavailable")})

	_, err := adapter.resolveTarget(context.Background(), "3fe2bad9-3eae-4f23-872c-b7a63662aa00")
	if err == nil {
		t.Fatal("expected identity resolver error")
	}
	if !strings.Contains(err.Error(), "identity store unavailable") {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestParseTargetRejectsUUIDForC2C(t *testing.T) {
	t.Parallel()

	_, err := parseTarget("3fe2bad9-3eae-4f23-872c-b7a63662aa00")
	if err == nil {
		t.Fatal("expected c2c uuid target error")
	}
	if !strings.Contains(err.Error(), "user_openid") {
		t.Fatalf("unexpected error: %v", err)
	}
}

type fakeQQIdentityResolver struct {
	byID         map[string]identitypkg.ChannelIdentity
	canonical    map[string][]identitypkg.ChannelIdentity
	userScoped   map[string][]identitypkg.ChannelIdentity
	byIDErr      error
	canonicalErr error
	userErr      error
}

func (f *fakeQQIdentityResolver) GetByID(_ context.Context, channelIdentityID string) (identitypkg.ChannelIdentity, error) {
	if f.byIDErr != nil {
		return identitypkg.ChannelIdentity{}, f.byIDErr
	}
	item, ok := f.byID[channelIdentityID]
	if !ok {
		return identitypkg.ChannelIdentity{}, identitypkg.ErrChannelIdentityNotFound
	}
	return item, nil
}

func (f *fakeQQIdentityResolver) ListCanonicalChannelIdentities(_ context.Context, channelIdentityID string) ([]identitypkg.ChannelIdentity, error) {
	if f.canonicalErr != nil {
		return nil, f.canonicalErr
	}
	items, ok := f.canonical[channelIdentityID]
	if !ok {
		return nil, identitypkg.ErrChannelIdentityNotFound
	}
	return items, nil
}

func (f *fakeQQIdentityResolver) ListUserChannelIdentities(_ context.Context, userID string) ([]identitypkg.ChannelIdentity, error) {
	if f.userErr != nil {
		return nil, f.userErr
	}
	items, ok := f.userScoped[userID]
	if !ok {
		return nil, nil
	}
	return items, nil
}

type fakeQQRouteResolver struct {
	byID map[string]routepkg.Route
	err  error
}

func (f *fakeQQRouteResolver) GetByID(_ context.Context, routeID string) (routepkg.Route, error) {
	if f.err != nil {
		return routepkg.Route{}, f.err
	}
	item, ok := f.byID[routeID]
	if !ok {
		return routepkg.Route{}, pgx.ErrNoRows
	}
	return item, nil
}
