# 导入必要的模块：JSON处理、单元测试Mock工具、Django配置/认证/测试/URL工具、项目工具类及OAuth相关模型和管理器
import json
from unittest.mock import patch  # 用于Mock测试中替换真实函数/方法，模拟第三方接口返回

from django.conf import settings  # 导入Django项目配置
from django.contrib import auth  # 导入Django认证模块，用于获取/验证用户
from django.test import Client, RequestFactory, TestCase  # Django测试基础类：Client模拟HTTP请求，RequestFactory构建请求对象，TestCase测试基类
from django.urls import reverse  # 用于通过URL名称反向解析URL

from djangoblog.utils import get_sha256  # 导入项目自定义工具类，用于生成SHA256加密字符串
from oauth.models import OAuthConfig  # 导入OAuth配置模型，用于测试中创建配置数据
from oauth.oauthmanager import BaseOauthManager  # 导入OAuth管理器基类，用于获取所有第三方登录管理器子类


# Create your tests here.
# 定义OAuth配置相关的测试类，继承Django测试基类TestCase
class OAuthConfigTest(TestCase):
    # 测试前置方法：在每个测试方法执行前初始化测试环境
    def setUp(self):
        self.client = Client()  # 创建HTTP客户端对象，用于发送测试请求
        self.factory = RequestFactory()  # 创建请求工厂对象，用于构建自定义请求

    # 测试OAuth登录流程的基础功能（以微博为例）
    def test_oauth_login_test(self):
        # 1. 创建测试用的微博OAuth配置数据并保存到数据库
        c = OAuthConfig()
        c.type = 'weibo'  # 平台类型为微博
        c.appkey = 'appkey'  # 模拟AppKey
        c.appsecret = 'appsecret'  # 模拟AppSecret
        c.save()  # 保存到数据库

        # 2. 发送GET请求到微博OAuth登录地址，测试跳转是否正常
        response = self.client.get('/oauth/oauthlogin?type=weibo')
        self.assertEqual(response.status_code, 302)  # 断言响应状态码为302（重定向，符合OAuth授权流程）
        self.assertTrue("api.weibo.com" in response.url)  # 断言重定向URL包含微博API域名，确认跳转到微博授权页

        # 3. 模拟授权成功后回调，发送带code参数的请求到授权处理地址
        response = self.client.get('/oauth/authorize?type=weibo&code=code')
        self.assertEqual(response.status_code, 302)  # 断言响应状态码为302（处理后重定向）
        self.assertEqual(response.url, '/')  # 断言重定向到首页，确认授权后跳转正常


# 定义第三方登录流程的测试类，继承TestCase
class OauthLoginTest(TestCase):
    # 测试前置方法：初始化客户端、请求工厂，并创建所有支持的第三方平台配置
    def setUp(self) -> None:
        self.client = Client()  # 初始化HTTP客户端
        self.factory = RequestFactory()  # 初始化请求工厂
        self.apps = self.init_apps()  # 调用自定义方法，创建所有第三方平台的测试配置

    # 初始化所有第三方登录平台的配置（基于BaseOauthManager的子类）
    def init_apps(self):
        # 1. 获取BaseOauthManager的所有子类（即各第三方平台的具体管理器，如微博、谷歌等）
        applications = [p() for p in BaseOauthManager.__subclasses__()]
        # 2. 为每个平台创建对应的OAuthConfig数据并保存
        for application in applications:
            c = OAuthConfig()
            c.type = application.ICON_NAME.lower()  # 平台类型与管理器的ICON_NAME一致（小写）
            c.appkey = 'appkey'  # 模拟AppKey
            c.appsecret = 'appsecret'  # 模拟AppSecret
            c.save()  # 保存到数据库
        return applications  # 返回所有平台管理器实例，供后续测试使用

    # 根据平台类型获取对应的管理器实例
    def get_app_by_type(self, type):
        for app in self.apps:
            if app.ICON_NAME.lower() == type:  # 匹配平台类型（小写）
                return app
        return None  # 未找到时返回None

    # 测试微博登录流程（使用@patch模拟管理器的do_post/do_get方法，避免真实调用第三方接口）
    @patch("oauth.oauthmanager.WBOauthManager.do_post")  # Mock微博管理器的POST请求方法
    @patch("oauth.oauthmanager.WBOauthManager.do_get")   # Mock微博管理器的GET请求方法
    def test_weibo_login(self, mock_do_get, mock_do_post):
        # 1. 获取微博管理器实例，断言实例存在（确认初始化成功）
        weibo_app = self.get_app_by_type('weibo')
        assert weibo_app

        # 2. 调用获取授权URL的方法（仅验证方法可执行，未断言URL内容）
        url = weibo_app.get_authorization_url()

        # 3. 模拟do_post返回（第三方平台返回的access_token和uid）
        mock_do_post.return_value = json.dumps({"access_token": "access_token",
                                                "uid": "uid"
                                                })
        # 4. 模拟do_get返回（第三方平台返回的用户信息，如头像、昵称等）
        mock_do_get.return_value = json.dumps({
            "avatar_large": "avatar_large",
            "screen_name": "screen_name",
            "id": "id",
            "email": "email",
        })

        # 5. 调用获取access_token的方法，获取用户信息对象
        userinfo = weibo_app.get_access_token_by_code('code')
        # 6. 断言用户信息中的token和openid与模拟返回一致（验证流程正确性）
        self.assertEqual(userinfo.token, 'access_token')
        self.assertEqual(userinfo.openid, 'id')

    # 测试谷歌登录流程（逻辑与微博类似，Mock谷歌管理器的请求方法）
    @patch("oauth.oauthmanager.GoogleOauthManager.do_post")
    @patch("oauth.oauthmanager.GoogleOauthManager.do_get")
    def test_google_login(self, mock_do_get, mock_do_post):
        # 1. 获取谷歌管理器实例，断言存在
        google_app = self.get_app_by_type('google')
        assert google_app

        # 2. 调用获取授权URL的方法
        url = google_app.get_authorization_url()

        # 3. 模拟do_post返回（谷歌返回的access_token和id_token）
        mock_do_post.return_value = json.dumps({
            "access_token": "access_token",
            "id_token": "id_token",
        })
        # 4. 模拟do_get返回（谷歌返回的用户信息）
        mock_do_get.return_value = json.dumps({
            "picture": "picture",
            "name": "name",
            "sub": "sub",  # 谷歌用户唯一标识（对应openid）
            "email": "email",
        })

        # 5. 调用获取token和用户信息的方法
        token = google_app.get_access_token_by_code('code')
        userinfo = google_app.get_oauth_userinfo()
        # 6. 断言token和openid与模拟返回一致
        self.assertEqual(userinfo.token, 'access_token')
        self.assertEqual(userinfo.openid, 'sub')

    # 测试GitHub登录流程
    @patch("oauth.oauthmanager.GitHubOauthManager.do_post")
    @patch("oauth.oauthmanager.GitHubOauthManager.do_get")
    def test_github_login(self, mock_do_get, mock_do_post):
        # 1. 获取GitHub管理器实例，断言存在
        github_app = self.get_app_by_type('github')
        assert github_app

        # 2. 调用获取授权URL，断言URL包含GitHub域名和client_id（验证授权URL正确性）
        url = github_app.get_authorization_url()
        self.assertTrue("github.com" in url)  # 确认跳转到GitHub授权页
        self.assertTrue("client_id" in url)   # 确认URL包含client_id参数

        # 3. 模拟do_post返回（GitHub返回的token字符串，非JSON格式）
        mock_do_post.return_value = "access_token=gho_16C7e42F292c6912E7710c838347Ae178B4a&scope=repo%2Cgist&token_type=bearer"
        # 4. 模拟do_get返回（GitHub返回的用户信息）
        mock_do_get.return_value = json.dumps({
            "avatar_url": "avatar_url",
            "name": "name",
            "id": "id",  # GitHub用户唯一标识
            "email": "email",
        })

        # 5. 调用获取token和用户信息的方法
        token = github_app.get_access_token_by_code('code')
        userinfo = github_app.get_oauth_userinfo()
        # 6. 断言token与模拟返回一致
        self.assertEqual(userinfo.token, 'gho_16C7e42F292c6912E7710c838347Ae178B4a')
        self.assertEqual(userinfo.openid, 'id')

    # 测试Facebook登录流程
    @patch("oauth.oauthmanager.FaceBookOauthManager.do_post")
    @patch("oauth.oauthmanager.FaceBookOauthManager.do_get")
    def test_facebook_login(self, mock_do_get, mock_do_post):
        # 1. 获取Facebook管理器实例，断言存在
        facebook_app = self.get_app_by_type('facebook')
        assert facebook_app

        # 2. 调用获取授权URL，断言包含Facebook域名
        url = facebook_app.get_authorization_url()
        self.assertTrue("facebook.com" in url)

        # 3. 模拟do_post返回（Facebook返回的access_token）
        mock_do_post.return_value = json.dumps({
            "access_token": "access_token",
        })
        # 4. 模拟do_get返回（Facebook返回的用户信息，头像嵌套在picture.data.url中）
        mock_do_get.return_value = json.dumps({
            "name": "name",
            "id": "id",  # Facebook用户唯一标识
            "email": "email",
            "picture": {
                "data": {
                    "url": "url"
                }
            }
        })

        # 5. 调用获取token和用户信息的方法
        token = facebook_app.get_access_token_by_code('code')
        userinfo = facebook_app.get_oauth_userinfo()
        # 6. 断言token与模拟返回一致
        self.assertEqual(userinfo.token, 'access_token')

    # 测试QQ登录流程（Mock do_get方法，模拟多步返回：token、openid、用户信息）
    @patch("oauth.oauthmanager.QQOauthManager.do_get", side_effect=[
        # 模拟三次do_get调用的返回值（QQ授权流程需多步请求）
        'access_token=access_token&expires_in=3600',  # 第一步：获取token
        'callback({"client_id":"appid","openid":"openid"} );',  # 第二步：获取openid（带callback包裹）
        json.dumps({  # 第三步：获取用户信息
            "nickname": "nickname",
            "email": "email",
            "figureurl": "figureurl",
            "openid": "openid",
        })
    ])
    def test_qq_login(self, mock_do_get):
        # 1. 获取QQ管理器实例，断言存在
        qq_app = self.get_app_by_type('qq')
        assert qq_app

        # 2. 调用获取授权URL，断言包含QQ域名
        url = qq_app.get_authorization_url()
        self.assertTrue("qq.com" in url)

        # 3. 调用获取token和用户信息的方法
        token = qq_app.get_access_token_by_code('code')
        userinfo = qq_app.get_oauth_userinfo()
        # 4. 断言token与模拟返回一致
        self.assertEqual(userinfo.token, 'access_token')

    # 测试微博登录（用户信息包含邮箱的场景）：验证登录后自动绑定用户、跳转首页
    @patch("oauth.oauthmanager.WBOauthManager.do_post")
    @patch("oauth.oauthmanager.WBOauthManager.do_get")
    def test_weibo_authoriz_login_with_email(self, mock_do_get, mock_do_post):
        # 1. 模拟do_post返回（access_token和uid）
        mock_do_post.return_value = json.dumps({"access_token": "access_token",
                                                "uid": "uid"
                                                })
        # 2. 模拟do_get返回（包含邮箱的用户信息）
        mock_user_info = {
            "avatar_large": "avatar_large",
            "screen_name": "screen_name1",  # 昵称（将作为Django用户名）
            "id": "id",
            "email": "email",  # 包含邮箱
        }
        mock_do_get.return_value = json.dumps(mock_user_info)

        # 3. 发送请求到微博登录地址，断言重定向到微博API
        response = self.client.get('/oauth/oauthlogin?type=weibo')
        self.assertEqual(response.status_code, 302)
        self.assertTrue("api.weibo.com" in response.url)

        # 4. 模拟授权回调，发送带code的请求到授权处理地址
        response = self.client.get('/oauth/authorize?type=weibo&code=code')
        self.assertEqual(response.status_code, 302)
        self.assertEqual(response.url, '/')  # 断言授权后跳转到首页

        # 5. 验证用户已登录，且用户信息与模拟数据一致
        user = auth.get_user(self.client)  # 获取当前登录用户
        assert user.is_authenticated  # 断言用户已认证
        self.assertTrue(user.is_authenticated)
        self.assertEqual(user.username, mock_user_info['screen_name'])  # 用户名=微博昵称
        self.assertEqual(user.email, mock_user_info['email'])  # 邮箱=微博返回的邮箱

        # 6. 测试登出后再次登录（验证重复登录逻辑）
        self.client.logout()  # 登出当前用户
        response = self.client.get('/oauth/authorize?type=weibo&code=code')  # 再次授权
        self.assertEqual(response.status_code, 302)
        self.assertEqual(response.url, '/')

        # 7. 再次验证用户登录状态和信息
        user = auth.get_user(self.client)
        assert user.is_authenticated
        self.assertTrue(user.is_authenticated)
        self.assertEqual(user.username, mock_user_info['screen_name'])
        self.assertEqual(user.email, mock_user_info['email'])

    # 测试微博登录（用户信息不含邮箱的场景）：验证跳转邮箱填写页、绑定邮箱后登录
    @patch("oauth.oauthmanager.WBOauthManager.do_post")
    @patch("oauth.oauthmanager.WBOauthManager.do_get")
    def test_weibo_authoriz_login_without_email(self, mock_do_get, mock_do_post):
        # 1. 模拟do_post返回（access_token和uid）
        mock_do_post.return_value = json.dumps({"access_token": "access_token",
                                                "uid": "uid"
                                                })
        # 2. 模拟do_get返回（不含邮箱的用户信息）
        mock_user_info = {
            "avatar_large": "avatar_large",
            "screen_name": "screen_name1",
            "id": "id",
        }
        mock_do_get.return_value = json.dumps(mock_user_info)

        # 3. 发送请求到微博登录地址，断言重定向到微博API
        response = self.client.get('/oauth/oauthlogin?type=weibo')
        self.assertEqual(response.status_code, 302)
        self.assertTrue("api.weibo.com" in response.url)

        # 4. 模拟授权回调：因无邮箱，应跳转到邮箱填写页
        response = self.client.get('/oauth/authorize?type=weibo&code=code')
        self.assertEqual(response.status_code, 302)  # 重定向到邮箱填写页

        # 5. 解析邮箱填写页URL中的oauth_user_id（第三方用户记录ID）
        oauth_user_id = int(response.url.split('/')[-1].split('.')[0])
        self.assertEqual(response.url, f'/oauth/requireemail/{oauth_user_id}.html')  # 断言跳转地址正确

        # 6. 模拟提交邮箱：发送POST请求到邮箱填写页，传入邮箱和oauth_user_id
        response = self.client.post(response.url, {'email': 'test@gmail.com', 'oauthid': oauth_user_id})
        self.assertEqual(response.status_code, 302)  # 提交后重定向到绑定成功页

        # 7. 生成邮箱验证的签名（使用项目工具类，基于SECRET_KEY和oauth_user_id）
        sign = get_sha256(settings.SECRET_KEY + str(oauth_user_id) + settings.SECRET_KEY)

        # 8. 反向解析绑定成功页URL，断言提交邮箱后跳转地址正确
        url = reverse('oauth:bindsuccess', kwargs={'oauthid': oauth_user_id})
        self.assertEqual(response.url, f'{url}?type=email')  # 跳转带type=email参数的绑定成功页

        # 9. 模拟访问邮箱验证链接（确认邮箱绑定）
        path = reverse('oauth:email_confirm', kwargs={'id': oauth_user_id, 'sign': sign})
        response = self.client.get(path)
        self.assertEqual(response.status_code, 302)
        # 断言验证后跳转到带type=success的绑定成功页
        self.assertEqual(response.url, f'/oauth/bindsuccess/{oauth_user_id}.html?type=success')

        # 10. 验证用户已登录，且信息与填写的邮箱一致
        user = auth.get_user(self.client)
        from oauth.models import OAuthUser  # 导入OAuthUser模型（避免循环导入）
        oauth_user = OAuthUser.objects.get(author=user)  # 获取关联的第三方用户记录
        self.assertTrue(user.is_authenticated)  # 断言用户已认证
        self.assertEqual(user.username, mock_user_info['screen_name'])  # 用户名=微博昵称
        self.assertEqual(user.email, 'test@gmail.com')  # 邮箱=填写的测试邮箱
        self.assertEqual(oauth_user.pk, oauth_user_id)  # 关联的第三方用户ID正确