package http

import (
	"context"
	"encoding/json"
	"hyapp/services/gateway-service/internal/transport/http/httpkit"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	userv1 "hyapp.local/api/proto/user/v1"
	"hyapp/services/gateway-service/internal/auth"
)

type fakeUserSocialClient struct {
	lastVisit      *userv1.RecordProfileVisitRequest
	lastVisitors   *userv1.ListProfileVisitorsRequest
	lastFollow     *userv1.FollowUserRequest
	lastUnfollow   *userv1.UnfollowUserRequest
	lastFollowing  *userv1.ListFollowingRequest
	lastApply      *userv1.ApplyFriendRequest
	lastAccept     *userv1.AcceptFriendApplicationRequest
	lastDelete     *userv1.DeleteFriendRequest
	lastFriends    *userv1.ListFriendsRequest
	lastFriendApps *userv1.ListFriendApplicationsRequest
	lastReport     *userv1.SubmitReportRequest
}

func (f *fakeUserSocialClient) RecordProfileVisit(_ context.Context, req *userv1.RecordProfileVisitRequest) (*userv1.RecordProfileVisitResponse, error) {
	f.lastVisit = req
	return &userv1.RecordProfileVisitResponse{Recorded: true, TargetStats: &userv1.UserProfileStats{UserId: req.GetTargetUserId(), VisitorsCount: 1, FollowersCount: 2, FriendsCount: 3}}, nil
}

func (f *fakeUserSocialClient) ListProfileVisitors(_ context.Context, req *userv1.ListProfileVisitorsRequest) (*userv1.ListProfileVisitorsResponse, error) {
	f.lastVisitors = req
	return &userv1.ListProfileVisitorsResponse{Records: []*userv1.ProfileVisitRecord{{VisitorUserId: 10002, TargetUserId: req.GetUserId(), VisitCount: 2, LastVisitedAtMs: 7000}}, Total: 1}, nil
}

func (f *fakeUserSocialClient) FollowUser(_ context.Context, req *userv1.FollowUserRequest) (*userv1.FollowUserResponse, error) {
	f.lastFollow = req
	return &userv1.FollowUserResponse{Following: true, FollowerStats: &userv1.UserProfileStats{UserId: req.GetFollowerUserId(), FollowingCount: 1}}, nil
}

func (f *fakeUserSocialClient) UnfollowUser(_ context.Context, req *userv1.UnfollowUserRequest) (*userv1.UnfollowUserResponse, error) {
	f.lastUnfollow = req
	return &userv1.UnfollowUserResponse{Following: false, FollowerStats: &userv1.UserProfileStats{UserId: req.GetFollowerUserId()}}, nil
}

func (f *fakeUserSocialClient) ListFollowing(_ context.Context, req *userv1.ListFollowingRequest) (*userv1.ListFollowingResponse, error) {
	f.lastFollowing = req
	return &userv1.ListFollowingResponse{Records: []*userv1.FollowRecord{{FollowerUserId: req.GetUserId(), FolloweeUserId: 10002, FollowedAtMs: 8000}}, Total: 1}, nil
}

func (f *fakeUserSocialClient) ApplyFriend(_ context.Context, req *userv1.ApplyFriendRequest) (*userv1.ApplyFriendResponse, error) {
	f.lastApply = req
	return &userv1.ApplyFriendResponse{Application: &userv1.FriendApplication{RequesterUserId: req.GetRequesterUserId(), TargetUserId: req.GetTargetUserId(), Status: "pending", CreatedAtMs: 9000, UpdatedAtMs: 9000}}, nil
}

func (f *fakeUserSocialClient) AcceptFriendApplication(_ context.Context, req *userv1.AcceptFriendApplicationRequest) (*userv1.AcceptFriendApplicationResponse, error) {
	f.lastAccept = req
	return &userv1.AcceptFriendApplicationResponse{Friend: &userv1.FriendRecord{UserId: req.GetAccepterUserId(), FriendUserId: req.GetRequesterUserId(), FriendedAtMs: 10000}}, nil
}

func (f *fakeUserSocialClient) DeleteFriend(_ context.Context, req *userv1.DeleteFriendRequest) (*userv1.DeleteFriendResponse, error) {
	f.lastDelete = req
	return &userv1.DeleteFriendResponse{Deleted: true}, nil
}

func (f *fakeUserSocialClient) ListFriends(_ context.Context, req *userv1.ListFriendsRequest) (*userv1.ListFriendsResponse, error) {
	f.lastFriends = req
	return &userv1.ListFriendsResponse{Records: []*userv1.FriendRecord{{UserId: req.GetUserId(), FriendUserId: 10002, FriendedAtMs: 10000}}, Total: 1}, nil
}

func (f *fakeUserSocialClient) ListFriendApplications(_ context.Context, req *userv1.ListFriendApplicationsRequest) (*userv1.ListFriendApplicationsResponse, error) {
	f.lastFriendApps = req
	return &userv1.ListFriendApplicationsResponse{Applications: []*userv1.FriendApplication{{RequesterUserId: 10002, TargetUserId: req.GetUserId(), Status: "pending", CreatedAtMs: 9000, UpdatedAtMs: 9000}}, Total: 1}, nil
}

func (f *fakeUserSocialClient) SubmitReport(_ context.Context, req *userv1.SubmitReportRequest) (*userv1.SubmitReportResponse, error) {
	f.lastReport = req
	targetType := "user"
	if req.GetRoomId() != "" {
		targetType = "room"
	}
	return &userv1.SubmitReportResponse{Report: &userv1.UserReport{
		ReportId:       "rpt-test",
		ReporterUserId: req.GetReporterUserId(),
		TargetType:     targetType,
		UserId:         req.GetUserId(),
		RoomId:         req.GetRoomId(),
		ReportType:     req.GetReportType(),
		Reason:         req.GetReason(),
		ImageUrls:      req.GetImageUrls(),
		Status:         "submitted",
		CreatedAtMs:    1778000000000,
	}}, nil
}

func TestFriendApplicationFlowRoutesUseBackendThenIMOwnedReminder(t *testing.T) {
	socialClient := &fakeUserSocialClient{}
	handler := NewHandler(&fakeRoomClient{})
	handler.SetUserSocialClient(socialClient)
	router := handler.Routes(auth.NewVerifier("secret"))

	applyReq := httptest.NewRequest(http.MethodPost, "/api/v1/users/10002/friend-requests", nil)
	applyReq.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 10001))
	applyResp := httptest.NewRecorder()
	router.ServeHTTP(applyResp, applyReq)
	if applyResp.Code != http.StatusOK {
		t.Fatalf("apply friend status = %d body=%s", applyResp.Code, applyResp.Body.String())
	}
	if socialClient.lastApply == nil || socialClient.lastApply.GetRequesterUserId() != 10001 || socialClient.lastApply.GetTargetUserId() != 10002 {
		t.Fatalf("apply friend request mismatch: %+v", socialClient.lastApply)
	}

	acceptReq := httptest.NewRequest(http.MethodPost, "/api/v1/users/10001/friend-requests/accept", nil)
	acceptReq.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 10002))
	acceptResp := httptest.NewRecorder()
	router.ServeHTTP(acceptResp, acceptReq)
	if acceptResp.Code != http.StatusOK {
		t.Fatalf("accept friend status = %d body=%s", acceptResp.Code, acceptResp.Body.String())
	}
	if socialClient.lastAccept == nil || socialClient.lastAccept.GetAccepterUserId() != 10002 || socialClient.lastAccept.GetRequesterUserId() != 10001 {
		t.Fatalf("accept friend request mismatch: %+v", socialClient.lastAccept)
	}
}

func TestProfileVisitReturnsTargetProfileBackground(t *testing.T) {
	socialClient := &fakeUserSocialClient{}
	profileClient := &fakeUserProfileClient{usersByID: map[int64]*userv1.User{10002: {
		UserId:        10002,
		DisplayUserId: "10002",
		Username:      "target",
		ProfileBgImg:  "https://cdn.example/target-profile-bg.png",
	}}}
	handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient)
	handler.SetUserSocialClient(socialClient)
	router := handler.Routes(auth.NewVerifier("secret"))
	request := httptest.NewRequest(http.MethodPost, "/api/v1/users/10002/visit", nil)
	request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 10001))
	request.Header.Set("X-Request-ID", "req-visit-profile-bg")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	if response.Code != http.StatusOK {
		t.Fatalf("profile visit status = %d body=%s", response.Code, response.Body.String())
	}
	if socialClient.lastVisit == nil || socialClient.lastVisit.GetVisitorUserId() != 10001 || socialClient.lastVisit.GetTargetUserId() != 10002 {
		t.Fatalf("visit request mismatch: %+v", socialClient.lastVisit)
	}
	if profileClient.lastGet == nil || profileClient.lastGet.GetUserId() != 10002 {
		t.Fatalf("target profile request mismatch: %+v", profileClient.lastGet)
	}
	var envelope httpkit.ResponseEnvelope
	if err := json.Unmarshal(response.Body.Bytes(), &envelope); err != nil || envelope.Code != httpkit.CodeOK {
		t.Fatalf("invalid envelope: %+v err=%v", envelope, err)
	}
	data, ok := envelope.Data.(map[string]any)
	if !ok {
		t.Fatalf("invalid data: %+v", envelope.Data)
	}
	profile, ok := data["target_profile"].(map[string]any)
	if !ok || profile["user_id"] != "10002" || profile["profile_bg_img"] != "https://cdn.example/target-profile-bg.png" {
		t.Fatalf("target profile background mismatch: %+v", data["target_profile"])
	}
	stats, ok := data["target_stats"].(map[string]any)
	if !ok || stats["followers_count"] != float64(2) || stats["friends_count"] != float64(2) {
		t.Fatalf("target profile followers compatibility mismatch: %+v", data["target_stats"])
	}
}

func TestSubmitReportRoutePassesAuthenticatedReporterAndSingleTarget(t *testing.T) {
	socialClient := &fakeUserSocialClient{}
	handler := NewHandler(&fakeRoomClient{})
	handler.SetUserSocialClient(socialClient)
	router := handler.Routes(auth.NewVerifier("secret"))

	request := httptest.NewRequest(http.MethodPost, "/api/v1/reports", strings.NewReader(`{"user_id":"10002","report_type":"pornography","reason":"bad","image_urls":["https://cdn.example.com/a.png"]}`))
	request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithProfile(t, "secret", 10001, true))
	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)
	if response.Code != http.StatusOK {
		t.Fatalf("submit report status = %d body=%s", response.Code, response.Body.String())
	}
	if socialClient.lastReport == nil || socialClient.lastReport.GetReporterUserId() != 10001 || socialClient.lastReport.GetUserId() != 10002 || socialClient.lastReport.GetRoomId() != "" || socialClient.lastReport.GetReportType() != "pornography" {
		t.Fatalf("submit report request mismatch: %+v", socialClient.lastReport)
	}
	var envelope httpkit.ResponseEnvelope
	if err := json.Unmarshal(response.Body.Bytes(), &envelope); err != nil || envelope.Code != httpkit.CodeOK {
		t.Fatalf("invalid envelope: %+v err=%v", envelope, err)
	}
}

func TestSocialListRoutesPassPagingAndDirection(t *testing.T) {
	socialClient := &fakeUserSocialClient{}
	handler := NewHandler(&fakeRoomClient{})
	handler.SetUserSocialClient(socialClient)
	router := handler.Routes(auth.NewVerifier("secret"))

	request := httptest.NewRequest(http.MethodGet, "/api/v1/users/me/friend-requests?direction=outgoing&page=2&page_size=30", nil)
	request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 10001))
	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)
	if response.Code != http.StatusOK {
		t.Fatalf("list friend applications status = %d body=%s", response.Code, response.Body.String())
	}
	if socialClient.lastFriendApps == nil || socialClient.lastFriendApps.GetUserId() != 10001 || socialClient.lastFriendApps.GetDirection() != "outgoing" || socialClient.lastFriendApps.GetPage() != 2 || socialClient.lastFriendApps.GetPageSize() != 30 {
		t.Fatalf("friend application list request mismatch: %+v", socialClient.lastFriendApps)
	}
	var envelope httpkit.ResponseEnvelope
	if err := json.Unmarshal(response.Body.Bytes(), &envelope); err != nil || envelope.Code != httpkit.CodeOK {
		t.Fatalf("invalid envelope: %+v err=%v", envelope, err)
	}
}
