package providers

import (
	"context"
	"io"
	"net/http"
	"net/url"
	"strings"
	"testing"
)

func parseRequestForm(t *testing.T, req *http.Request) url.Values {
	t.Helper()

	body, err := io.ReadAll(req.Body)
	if err != nil {
		t.Fatalf("read request body failed: %v", err)
	}
	values, err := url.ParseQuery(string(body))
	if err != nil {
		t.Fatalf("parse request body failed: %v", err)
	}
	return values
}

func TestPostFormWithContextSendsEncodedBody(t *testing.T) {
	client := &http.Client{
		Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.Method != http.MethodPost {
				t.Fatalf("expected POST request, got %s", req.Method)
			}
			if req.URL.String() != "https://oauth.example.com/token" {
				t.Fatalf("unexpected endpoint: %s", req.URL.String())
			}
			if req.Header.Get("Content-Type") != "application/x-www-form-urlencoded" {
				t.Fatalf("unexpected content type: %s", req.Header.Get("Content-Type"))
			}

			form := parseRequestForm(t, req)
			if form.Get("code") != "auth-code" || form.Get("grant_type") != "authorization_code" {
				t.Fatalf("unexpected form payload: %#v", form)
			}

			return oauthResponse(`{"ok":true}`), nil
		}),
	}

	resp, err := postFormWithContext(context.Background(), client, "https://oauth.example.com/token", url.Values{
		"code":       {"auth-code"},
		"grant_type": {"authorization_code"},
	})
	if err != nil {
		t.Fatalf("postFormWithContext failed: %v", err)
	}
	defer resp.Body.Close()
}

func TestAlipayProviderExchangeCodeAndGetUserInfo(t *testing.T) {
	ctx := context.Background()
	provider := NewAlipayProvider("alipay-app", "", "https://example.com/callback", false)

	t.Run("exchange code success", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.URL.Host != "openapi.alipay.com" || req.URL.Path != "/gateway.do" {
				t.Fatalf("unexpected request: %s", req.URL.String())
			}
			form := parseRequestForm(t, req)
			if form.Get("method") != "alipay.system.oauth.token" || form.Get("code") != "auth-code" {
				t.Fatalf("unexpected exchange payload: %#v", form)
			}
			return oauthResponse(`{"alipay_system_oauth_token_response":{"user_id":"2088","access_token":"ali-token","expires_in":3600}}`), nil
		}))

		tokenResp, err := provider.ExchangeCode(ctx, "auth-code")
		if err != nil {
			t.Fatalf("expected exchange success, got error %v", err)
		}
		if tokenResp.AccessToken != "ali-token" || tokenResp.UserID != "2088" {
			t.Fatalf("unexpected alipay token response: %#v", tokenResp)
		}
	})

	t.Run("exchange code rejects invalid structure", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			return oauthResponse(`{"unexpected":{}}`), nil
		}))

		_, err := provider.ExchangeCode(ctx, "auth-code")
		if err == nil || !strings.Contains(err.Error(), "invalid alipay response structure") {
			t.Fatalf("expected invalid structure error, got %v", err)
		}
	})

	t.Run("get user info success", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.URL.Host != "openapi.alipay.com" || req.URL.Path != "/gateway.do" {
				t.Fatalf("unexpected request: %s", req.URL.String())
			}
			form := parseRequestForm(t, req)
			if form.Get("method") != "alipay.user.info.share" || form.Get("auth_token") != "ali-token" {
				t.Fatalf("unexpected user-info payload: %#v", form)
			}
			return oauthResponse(`{"alipay_user_info_share_response":{"user_id":"2088","nick_name":"Ali User","avatar":"https://cdn.example.com/avatar.png"}}`), nil
		}))

		userInfo, err := provider.GetUserInfo(ctx, "ali-token")
		if err != nil {
			t.Fatalf("expected user info success, got error %v", err)
		}
		if userInfo.UserID != "2088" || userInfo.Nickname != "Ali User" {
			t.Fatalf("unexpected alipay user info: %#v", userInfo)
		}
	})

	t.Run("get user info rejects invalid structure", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			return oauthResponse(`{"unexpected":{}}`), nil
		}))

		_, err := provider.GetUserInfo(ctx, "ali-token")
		if err == nil || !strings.Contains(err.Error(), "invalid alipay user info response") {
			t.Fatalf("expected invalid user info response error, got %v", err)
		}
	})
}

func TestDouyinProviderExchangeCodeAndGetUserInfo(t *testing.T) {
	ctx := context.Background()
	provider := NewDouyinProvider("douyin-key", "douyin-secret", "https://example.com/callback")

	t.Run("exchange code success", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.URL.Host != "open.douyin.com" || req.URL.Path != "/oauth/access_token/" {
				t.Fatalf("unexpected request: %s", req.URL.String())
			}
			form := parseRequestForm(t, req)
			if form.Get("client_key") != "douyin-key" || form.Get("code") != "auth-code" {
				t.Fatalf("unexpected exchange payload: %#v", form)
			}
			return oauthResponse(`{"data":{"access_token":"douyin-token","open_id":"open-1"},"message":"success"}`), nil
		}))

		tokenResp, err := provider.ExchangeCode(ctx, "auth-code")
		if err != nil {
			t.Fatalf("expected exchange success, got error %v", err)
		}
		if tokenResp.Data.AccessToken != "douyin-token" || tokenResp.Data.OpenID != "open-1" {
			t.Fatalf("unexpected douyin token response: %#v", tokenResp)
		}
	})

	t.Run("exchange code rejects empty access token", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			return oauthResponse(`{"data":{},"message":"invalid code"}`), nil
		}))

		_, err := provider.ExchangeCode(ctx, "auth-code")
		if err == nil || !strings.Contains(err.Error(), "invalid code") {
			t.Fatalf("expected douyin api error, got %v", err)
		}
	})

	t.Run("get user info success", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.URL.Host != "open.douyin.com" || req.URL.Path != "/oauth/userinfo/" {
				t.Fatalf("unexpected request: %s", req.URL.String())
			}
			if req.URL.Query().Get("open_id") != "open-1" {
				t.Fatalf("unexpected open_id: %s", req.URL.Query().Get("open_id"))
			}
			return oauthResponse(`{"data":{"open_id":"open-1","union_id":"union-1","nickname":"Douyin User"}}`), nil
		}))

		userInfo, err := provider.GetUserInfo(ctx, "douyin-token", "open-1")
		if err != nil {
			t.Fatalf("expected user info success, got error %v", err)
		}
		if userInfo.Data.OpenID != "open-1" || userInfo.Data.Nickname != "Douyin User" {
			t.Fatalf("unexpected douyin user info: %#v", userInfo)
		}
	})
}

func TestGitHubProviderExchangeCodeAndGetUserInfo(t *testing.T) {
	ctx := context.Background()
	provider := NewGitHubProvider("github-client", "github-secret", "https://example.com/callback")

	t.Run("exchange code success", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.URL.Host != "github.com" || req.URL.Path != "/login/oauth/access_token" {
				t.Fatalf("unexpected request: %s", req.URL.String())
			}
			form := parseRequestForm(t, req)
			if form.Get("client_id") != "github-client" || form.Get("code") != "auth-code" {
				t.Fatalf("unexpected exchange payload: %#v", form)
			}
			return oauthResponse(`{"access_token":"gh-token","token_type":"bearer","scope":"read:user"}`), nil
		}))

		tokenResp, err := provider.ExchangeCode(ctx, "auth-code")
		if err != nil {
			t.Fatalf("expected exchange success, got error %v", err)
		}
		if tokenResp.AccessToken != "gh-token" {
			t.Fatalf("unexpected github token response: %#v", tokenResp)
		}
	})

	t.Run("exchange code rejects empty token", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			return oauthResponse(`{"token_type":"bearer"}`), nil
		}))

		_, err := provider.ExchangeCode(ctx, "auth-code")
		if err == nil || !strings.Contains(err.Error(), "empty access token") {
			t.Fatalf("expected empty access token error, got %v", err)
		}
	})

	t.Run("get user info falls back to primary email", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			switch req.URL.Host + req.URL.Path {
			case "api.github.com/user":
				if req.Header.Get("Authorization") != "Bearer gh-token" {
					t.Fatalf("unexpected auth header: %s", req.Header.Get("Authorization"))
				}
				return oauthResponse(`{"id":101,"login":"octocat","name":"The Octocat","email":"","avatar_url":"https://cdn.example.com/octocat.png"}`), nil
			case "api.github.com/user/emails":
				return oauthResponse(`[{"email":"secondary@example.com","primary":false,"verified":true},{"email":"primary@example.com","primary":true,"verified":true}]`), nil
			default:
				t.Fatalf("unexpected request: %s", req.URL.String())
				return nil, nil
			}
		}))

		userInfo, err := provider.GetUserInfo(ctx, "gh-token")
		if err != nil {
			t.Fatalf("expected user info success, got error %v", err)
		}
		if userInfo.Login != "octocat" || userInfo.Email != "primary@example.com" {
			t.Fatalf("unexpected github user info: %#v", userInfo)
		}
	})
}

func TestGoogleProviderExchangeCodeAndRefreshToken(t *testing.T) {
	ctx := context.Background()
	provider := NewGoogleProvider("google-client", "google-secret", "https://example.com/callback")

	t.Run("exchange code success", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.URL.Host != "oauth2.googleapis.com" || req.URL.Path != "/token" {
				t.Fatalf("unexpected request: %s", req.URL.String())
			}
			form := parseRequestForm(t, req)
			if form.Get("grant_type") != "authorization_code" || form.Get("code") != "auth-code" {
				t.Fatalf("unexpected exchange payload: %#v", form)
			}
			return oauthResponse(`{"access_token":"google-token","expires_in":3600,"refresh_token":"refresh-1","token_type":"Bearer"}`), nil
		}))

		tokenResp, err := provider.ExchangeCode(ctx, "auth-code")
		if err != nil {
			t.Fatalf("expected exchange success, got error %v", err)
		}
		if tokenResp.AccessToken != "google-token" || tokenResp.RefreshToken != "refresh-1" {
			t.Fatalf("unexpected google token response: %#v", tokenResp)
		}
	})

	t.Run("refresh token success", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.URL.Host != "oauth2.googleapis.com" || req.URL.Path != "/token" {
				t.Fatalf("unexpected request: %s", req.URL.String())
			}
			form := parseRequestForm(t, req)
			if form.Get("grant_type") != "refresh_token" || form.Get("refresh_token") != "refresh-1" {
				t.Fatalf("unexpected refresh payload: %#v", form)
			}
			return oauthResponse(`{"access_token":"google-token-2","expires_in":3600,"token_type":"Bearer"}`), nil
		}))

		tokenResp, err := provider.RefreshToken(ctx, "refresh-1")
		if err != nil {
			t.Fatalf("expected refresh success, got error %v", err)
		}
		if tokenResp.AccessToken != "google-token-2" {
			t.Fatalf("unexpected google refresh response: %#v", tokenResp)
		}
	})
}

func TestQQProviderExchangeCodeAndValidateToken(t *testing.T) {
	ctx := context.Background()
	provider := NewQQProvider("qq-app", "qq-secret", "https://example.com/callback")

	t.Run("exchange code success", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.URL.Host != "graph.qq.com" || req.URL.Path != "/oauth2.0/token" {
				t.Fatalf("unexpected request: %s", req.URL.String())
			}
			if req.URL.Query().Get("code") != "auth-code" {
				t.Fatalf("unexpected code: %s", req.URL.Query().Get("code"))
			}
			return oauthResponse(`{"access_token":"qq-token","expires_in":3600,"refresh_token":"qq-refresh"}`), nil
		}))

		tokenResp, err := provider.ExchangeCode(ctx, "auth-code")
		if err != nil {
			t.Fatalf("expected exchange success, got error %v", err)
		}
		if tokenResp.AccessToken != "qq-token" || tokenResp.RefreshToken != "qq-refresh" {
			t.Fatalf("unexpected qq token response: %#v", tokenResp)
		}
	})

	t.Run("validate token success", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.URL.Host != "graph.qq.com" || req.URL.Path != "/oauth2.0/me" {
				t.Fatalf("unexpected request: %s", req.URL.String())
			}
			return oauthResponse(`{"client_id":"qq-app","openid":"openid-1"}`), nil
		}))

		valid, err := provider.ValidateToken(ctx, "qq-token")
		if err != nil {
			t.Fatalf("expected validate success, got error %v", err)
		}
		if !valid {
			t.Fatal("expected qq token to be valid")
		}
	})
}

func TestTwitterProviderNetworkMethods(t *testing.T) {
	ctx := context.Background()
	provider := NewTwitterProvider("twitter-client", "https://example.com/callback")

	t.Run("exchange code rejects twitter error response", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.URL.Host != "api.twitter.com" || req.URL.Path != "/2/oauth2/token" {
				t.Fatalf("unexpected request: %s", req.URL.String())
			}
			form := parseRequestForm(t, req)
			if form.Get("grant_type") != "authorization_code" || form.Get("code_verifier") != "verifier-1" {
				t.Fatalf("unexpected exchange payload: %#v", form)
			}
			return oauthResponse(`{"title":"Unauthorized","detail":"invalid verifier","status":401}`), nil
		}))

		_, err := provider.ExchangeCode(ctx, "auth-code", "verifier-1")
		if err == nil || !strings.Contains(err.Error(), "invalid verifier") {
			t.Fatalf("expected twitter api error, got %v", err)
		}
	})

	t.Run("exchange code success", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			return oauthResponse(`{"access_token":"twitter-token","refresh_token":"twitter-refresh","token_type":"bearer"}`), nil
		}))

		tokenResp, err := provider.ExchangeCode(ctx, "auth-code", "verifier-1")
		if err != nil {
			t.Fatalf("expected exchange success, got error %v", err)
		}
		if tokenResp.AccessToken != "twitter-token" {
			t.Fatalf("unexpected twitter token response: %#v", tokenResp)
		}
	})

	t.Run("get user info rejects twitter error response", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.URL.Host != "api.twitter.com" || req.URL.Path != "/2/users/me" {
				t.Fatalf("unexpected request: %s", req.URL.String())
			}
			return oauthResponse(`{"title":"Unauthorized","detail":"token expired","status":401}`), nil
		}))

		_, err := provider.GetUserInfo(ctx, "twitter-token")
		if err == nil || !strings.Contains(err.Error(), "token expired") {
			t.Fatalf("expected twitter user info error, got %v", err)
		}
	})

	t.Run("get user info success", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			return oauthResponse(`{"data":{"id":"user-1","name":"Twitter User","username":"tw-user"}}`), nil
		}))

		userInfo, err := provider.GetUserInfo(ctx, "twitter-token")
		if err != nil {
			t.Fatalf("expected user info success, got error %v", err)
		}
		if userInfo.Data.ID != "user-1" || userInfo.Data.Username != "tw-user" {
			t.Fatalf("unexpected twitter user info: %#v", userInfo)
		}
	})

	t.Run("refresh token success", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			form := parseRequestForm(t, req)
			if form.Get("grant_type") != "refresh_token" || form.Get("refresh_token") != "twitter-refresh" {
				t.Fatalf("unexpected refresh payload: %#v", form)
			}
			return oauthResponse(`{"access_token":"twitter-token-2","refresh_token":"twitter-refresh-2","token_type":"bearer"}`), nil
		}))

		tokenResp, err := provider.RefreshToken(ctx, "twitter-refresh")
		if err != nil {
			t.Fatalf("expected refresh success, got error %v", err)
		}
		if tokenResp.AccessToken != "twitter-token-2" {
			t.Fatalf("unexpected twitter refresh response: %#v", tokenResp)
		}
	})

	t.Run("validate token returns false when user id is empty", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			return oauthResponse(`{"data":{"id":"","username":"anonymous"}}`), nil
		}))

		valid, err := provider.ValidateToken(ctx, "twitter-token")
		if err != nil {
			t.Fatalf("expected no error, got %v", err)
		}
		if valid {
			t.Fatal("expected twitter token to be reported invalid")
		}
	})

	t.Run("revoke token success", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.URL.Host != "api.twitter.com" || req.URL.Path != "/2/oauth2/revoke" {
				t.Fatalf("unexpected request: %s", req.URL.String())
			}
			form := parseRequestForm(t, req)
			if form.Get("token") != "twitter-token" || form.Get("token_type_hint") != "access_token" {
				t.Fatalf("unexpected revoke payload: %#v", form)
			}
			return oauthResponse(`{}`), nil
		}))

		if err := provider.RevokeToken(ctx, "twitter-token"); err != nil {
			t.Fatalf("expected revoke success, got error %v", err)
		}
	})
}

func TestWeChatProviderExchangeUserInfoAndRefreshToken(t *testing.T) {
	ctx := context.Background()
	provider := NewWeChatProvider("wx-app", "wx-secret", "web")

	t.Run("exchange code rejects api error", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.URL.Host != "api.weixin.qq.com" || req.URL.Path != "/sns/oauth2/access_token" {
				t.Fatalf("unexpected request: %s", req.URL.String())
			}
			return oauthResponse(`{"errcode":40029,"errmsg":"invalid code"}`), nil
		}))

		_, err := provider.ExchangeCode(ctx, "auth-code")
		if err == nil || !strings.Contains(err.Error(), "wechat api error: 40029 - invalid code") {
			t.Fatalf("expected wechat api error, got %v", err)
		}
	})

	t.Run("exchange code success", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			return oauthResponse(`{"access_token":"wx-token","refresh_token":"wx-refresh","openid":"openid-1","scope":"snsapi_login"}`), nil
		}))

		tokenResp, err := provider.ExchangeCode(ctx, "auth-code")
		if err != nil {
			t.Fatalf("expected exchange success, got error %v", err)
		}
		if tokenResp.AccessToken != "wx-token" || tokenResp.OpenID != "openid-1" {
			t.Fatalf("unexpected wechat token response: %#v", tokenResp)
		}
	})

	t.Run("get user info rejects api error", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.URL.Host != "api.weixin.qq.com" || req.URL.Path != "/sns/userinfo" {
				t.Fatalf("unexpected request: %s", req.URL.String())
			}
			return oauthResponse(`{"errcode":40003,"errmsg":"invalid openid"}`), nil
		}))

		_, err := provider.GetUserInfo(ctx, "wx-token", "openid-1")
		if err == nil || !strings.Contains(err.Error(), "wechat api error: 40003 - invalid openid") {
			t.Fatalf("expected wechat user info error, got %v", err)
		}
	})

	t.Run("get user info success", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			return oauthResponse(`{"openid":"openid-1","nickname":"WeChat User","province":"Shanghai"}`), nil
		}))

		userInfo, err := provider.GetUserInfo(ctx, "wx-token", "openid-1")
		if err != nil {
			t.Fatalf("expected user info success, got error %v", err)
		}
		if userInfo.OpenID != "openid-1" || userInfo.Nickname != "WeChat User" {
			t.Fatalf("unexpected wechat user info: %#v", userInfo)
		}
	})

	t.Run("refresh token rejects api error", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.URL.Host != "api.weixin.qq.com" || req.URL.Path != "/sns/oauth2/refresh_token" {
				t.Fatalf("unexpected request: %s", req.URL.String())
			}
			return oauthResponse(`{"errcode":40030,"errmsg":"invalid refresh token"}`), nil
		}))

		_, err := provider.RefreshToken(ctx, "wx-refresh")
		if err == nil || !strings.Contains(err.Error(), "wechat api error: 40030 - invalid refresh token") {
			t.Fatalf("expected wechat refresh error, got %v", err)
		}
	})

	t.Run("refresh token success", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			return oauthResponse(`{"access_token":"wx-token-2","refresh_token":"wx-refresh-2","openid":"openid-1"}`), nil
		}))

		tokenResp, err := provider.RefreshToken(ctx, "wx-refresh")
		if err != nil {
			t.Fatalf("expected refresh success, got error %v", err)
		}
		if tokenResp.AccessToken != "wx-token-2" {
			t.Fatalf("unexpected wechat refresh response: %#v", tokenResp)
		}
	})
}

func TestWeiboProviderExchangeCodeAndGetUserInfo(t *testing.T) {
	ctx := context.Background()
	provider := NewWeiboProvider("weibo-app", "weibo-secret", "https://example.com/callback")

	t.Run("exchange code success", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.URL.Host != "api.weibo.com" || req.URL.Path != "/oauth2/access_token" {
				t.Fatalf("unexpected request: %s", req.URL.String())
			}
			form := parseRequestForm(t, req)
			if form.Get("client_id") != "weibo-app" || form.Get("code") != "auth-code" {
				t.Fatalf("unexpected exchange payload: %#v", form)
			}
			return oauthResponse(`{"access_token":"weibo-token","expires_in":3600,"uid":"1001"}`), nil
		}))

		tokenResp, err := provider.ExchangeCode(ctx, "auth-code")
		if err != nil {
			t.Fatalf("expected exchange success, got error %v", err)
		}
		if tokenResp.AccessToken != "weibo-token" || tokenResp.UID != "1001" {
			t.Fatalf("unexpected weibo token response: %#v", tokenResp)
		}
	})

	t.Run("get user info rejects api error", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.URL.Host != "api.weibo.com" || req.URL.Path != "/2/users/show.json" {
				t.Fatalf("unexpected request: %s", req.URL.String())
			}
			return oauthResponse(`{"error":1,"error_code":21315,"request":"/2/users/show.json"}`), nil
		}))

		_, err := provider.GetUserInfo(ctx, "weibo-token", "1001")
		if err == nil || !strings.Contains(err.Error(), "weibo api error: code=21315") {
			t.Fatalf("expected weibo api error, got %v", err)
		}
	})

	t.Run("get user info success", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			return oauthResponse(`{"id":1001,"idstr":"1001","screen_name":"weibo-user","name":"Weibo User"}`), nil
		}))

		userInfo, err := provider.GetUserInfo(ctx, "weibo-token", "1001")
		if err != nil {
			t.Fatalf("expected user info success, got error %v", err)
		}
		if userInfo.ID != 1001 || userInfo.ScreenName != "weibo-user" {
			t.Fatalf("unexpected weibo user info: %#v", userInfo)
		}
	})
}

func TestFacebookProviderExchangeValidateAndLongLivedToken(t *testing.T) {
	ctx := context.Background()
	provider := NewFacebookProvider("facebook-app", "facebook-secret", "https://example.com/callback")

	t.Run("exchange code success", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.URL.Host != "graph.facebook.com" || req.URL.Path != "/v18.0/oauth/access_token" {
				t.Fatalf("unexpected request: %s", req.URL.String())
			}
			if req.URL.Query().Get("code") != "auth-code" {
				t.Fatalf("unexpected code: %s", req.URL.Query().Get("code"))
			}
			return oauthResponse(`{"access_token":"fb-token","token_type":"bearer","expires_in":3600}`), nil
		}))

		tokenResp, err := provider.ExchangeCode(ctx, "auth-code")
		if err != nil {
			t.Fatalf("expected exchange success, got error %v", err)
		}
		if tokenResp.AccessToken != "fb-token" {
			t.Fatalf("unexpected facebook token response: %#v", tokenResp)
		}
	})

	t.Run("validate token returns false for empty id", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.URL.Path != "/v18.0/me" {
				t.Fatalf("unexpected request: %s", req.URL.String())
			}
			return oauthResponse(`{"id":"","name":"No ID User"}`), nil
		}))

		valid, err := provider.ValidateToken(ctx, "fb-token")
		if err != nil {
			t.Fatalf("expected validate success, got error %v", err)
		}
		if valid {
			t.Fatal("expected facebook token to be reported invalid")
		}
	})

	t.Run("get long lived token success", func(t *testing.T) {
		useDefaultTransport(t, roundTripFunc(func(req *http.Request) (*http.Response, error) {
			if req.URL.Path != "/v18.0/oauth/access_token" || req.URL.Query().Get("grant_type") != "fb_exchange_token" {
				t.Fatalf("unexpected request: %s", req.URL.String())
			}
			return oauthResponse(`{"access_token":"fb-long-lived","token_type":"bearer","expires_in":5184000}`), nil
		}))

		tokenResp, err := provider.GetLongLivedToken(ctx, "fb-token")
		if err != nil {
			t.Fatalf("expected long-lived token success, got error %v", err)
		}
		if tokenResp.AccessToken != "fb-long-lived" {
			t.Fatalf("unexpected facebook long-lived token response: %#v", tokenResp)
		}
	})
}
