import logging  # 导入日志模块，用于记录系统运行日志
# Create your views here.
from urllib.parse import urlparse  # 导入URL解析工具，用于验证跳转URL的合法性

from django.conf import settings  # 导入Django项目配置
from django.contrib.auth import get_user_model  # 导入获取用户模型的工具（支持自定义用户模型）
from django.contrib.auth import login  # 导入登录函数，用于实现用户登录
from django.core.exceptions import ObjectDoesNotExist  # 导入对象不存在异常类
from django.db import transaction  # 导入数据库事务工具，确保数据操作的原子性
from django.http import HttpResponseForbidden  # 导入403禁止访问响应类
from django.http import HttpResponseRedirect  # 导入重定向响应类
from django.shortcuts import get_object_or_404  # 导入获取对象或返回404的工具
from django.shortcuts import render  # 导入渲染模板函数
from django.urls import reverse  # 导入URL反向解析工具
from django.utils import timezone  # 导入时间工具，用于生成时间相关的唯一标识
from django.utils.translation import gettext_lazy as _  # 导入国际化翻译工具
from django.views.generic import FormView  # 导入表单视图基类，用于处理表单提交

from djangoblog.blog_signals import oauth_user_login_signal  # 导入OAuth用户登录信号
from djangoblog.utils import get_current_site  # 导入获取当前站点信息的工具
from djangoblog.utils import send_email, get_sha256  # 导入发送邮件和SHA256加密工具
from oauth.forms import RequireEmailForm  # 导入获取邮箱的表单类
from .models import OAuthUser  # 导入OAuth用户模型
from .oauthmanager import get_manager_by_type, OAuthAccessTokenException  # 导入OAuth管理器和异常类

# 初始化日志记录器，用于记录当前模块的日志
logger = logging.getLogger(__name__)


def get_redirecturl(request):
    """
    处理跳转URL的验证和清洗，确保跳转地址合法
    :param request: HTTP请求对象
    :return: 清洗后的合法跳转URL
    """
    nexturl = request.GET.get('next_url', None)  # 从请求参数中获取跳转URL
    # 处理默认情况：无next_url或指向登录页时，默认跳转到首页
    if not nexturl or nexturl == '/login/' or nexturl == '/login':
        nexturl = '/'
        return nexturl
    # 解析URL，验证域名合法性（防止跳转到外部恶意网站）
    p = urlparse(nexturl)
    if p.netloc:  # 如果URL包含域名（非相对路径）
        site = get_current_site().domain  # 获取当前网站域名
        # 比较跳转URL的域名与当前网站域名（忽略www.前缀）
        if not p.netloc.replace('www.', '') == site.replace('www.', ''):
            logger.info('非法url:' + nexturl)  # 记录非法URL日志
            return "/"  # 非法URL时跳转到首页
    return nexturl  # 返回合法的跳转URL


def oauthlogin(request):
    """
    处理第三方登录请求，生成并跳转到第三方平台的授权页面
    :param request: HTTP请求对象
    :return: 重定向到第三方授权页面的响应
    """
    type = request.GET.get('type', None)  # 获取第三方平台类型（如weibo、github等）
    if not type:  # 无平台类型时跳转到首页
        return HttpResponseRedirect('/')
    # 根据平台类型获取对应的OAuth管理器（如微博管理器、GitHub管理器）
    manager = get_manager_by_type(type)
    if not manager:  # 管理器不存在时跳转到首页
        return HttpResponseRedirect('/')
    nexturl = get_redirecturl(request)  # 获取并验证跳转URL
    # 通过管理器生成第三方平台的授权URL（包含回调地址、state等参数）
    authorizeurl = manager.get_authorization_url(nexturl)
    return HttpResponseRedirect(authorizeurl)  # 重定向到第三方授权页面


def authorize(request):
    """
    处理第三方平台的授权回调，验证授权码并完成用户登录/绑定流程
    :param request: HTTP请求对象
    :return: 重定向到目标页面或邮箱填写页的响应
    """
    type = request.GET.get('type', None)  # 获取第三方平台类型
    if not type:  # 无平台类型时跳转到首页
        return HttpResponseRedirect('/')
    manager = get_manager_by_type(type)  # 获取对应的OAuth管理器
    if not manager:  # 管理器不存在时跳转到首页
        return HttpResponseRedirect('/')
    code = request.GET.get('code', None)  # 获取第三方平台返回的授权码
    try:
        # 使用授权码获取访问令牌（access token）
        rsp = manager.get_access_token_by_code(code)
    except OAuthAccessTokenException as e:  # 捕获令牌获取失败异常
        logger.warning("OAuthAccessTokenException:" + str(e))  # 记录警告日志
        return HttpResponseRedirect('/')
    except Exception as e:  # 捕获其他异常
        logger.error(e)  # 记录错误日志
        rsp = None
    nexturl = get_redirecturl(request)  # 获取验证后的跳转URL
    if not rsp:  # 令牌获取失败时，重新生成授权URL并跳转
        return HttpResponseRedirect(manager.get_authorization_url(nexturl))

    # 通过令牌获取第三方用户信息（如昵称、头像、邮箱等）
    user = manager.get_oauth_userinfo()
    if user:  # 成功获取用户信息
        # 处理空昵称：生成基于时间的默认昵称
        if not user.nickname or not user.nickname.strip():
            user.nickname = "djangoblog" + timezone.now().strftime('%y%m%d%I%M%S')

        try:
            # 查找是否已存在该第三方用户的关联记录
            temp = OAuthUser.objects.get(type=type, openid=user.openid)
            # 更新已有记录的头像、元数据和昵称
            temp.picture = user.picture
            temp.metadata = user.metadata
            temp.nickname = user.nickname
            user = temp  # 复用已有记录
        except ObjectDoesNotExist:
            # 不存在时使用新用户信息（后续会保存）
            pass

        # 特殊处理Facebook的token（因过长可能导致存储问题，此处清空）
        if type == 'facebook':
            user.token = ''

        if user.email:  # 若第三方用户信息包含邮箱（可直接绑定/创建本地用户）
            with transaction.atomic():  # 使用数据库事务确保操作原子性
                author = None
                try:
                    # 尝试通过关联ID获取本地用户
                    author = get_user_model().objects.get(id=user.author_id)
                except ObjectDoesNotExist:
                    pass  # 关联用户不存在时继续处理

                if not author:  # 本地用户不存在时，通过邮箱查找或创建
                    # 查找或创建与第三方邮箱一致的本地用户
                    result = get_user_model().objects.get_or_create(email=user.email)
                    author = result[0]
                    # 若为新创建的用户（result[1]为True）
                    if result[1]:
                        try:
                            # 检查昵称是否已被使用
                            get_user_model().objects.get(username=user.nickname)
                        except ObjectDoesNotExist:
                            # 未被使用则设置昵称
                            author.username = user.nickname
                        else:
                            # 已被使用则生成基于时间的唯一用户名
                            author.username = "djangoblog" + timezone.now().strftime('%y%m%d%I%M%S')
                        author.source = 'authorize'  # 标记用户来源为OAuth授权
                        author.save()  # 保存本地用户信息

                # 关联第三方用户记录与本地用户
                user.author = author
                user.save()  # 保存第三方用户记录

                # 发送OAuth用户登录信号（供其他模块监听处理）
                oauth_user_login_signal.send(
                    sender=authorize.__class__, id=user.id)
                login(request, author)  # 登录本地用户
                return HttpResponseRedirect(nexturl)  # 重定向到目标页面
        else:  # 第三方用户信息不含邮箱（需要用户补充邮箱）
            user.save()  # 先保存第三方用户记录（无关联本地用户）
            # 生成邮箱填写页的URL
            url = reverse('oauth:require_email', kwargs={'oauthid': user.id})
            return HttpResponseRedirect(url)  # 重定向到邮箱填写页
    else:  # 未获取到用户信息时，跳转到目标页面
        return HttpResponseRedirect(nexturl)


def emailconfirm(request, id, sign):
    """
    处理邮箱验证请求，完成第三方用户与本地用户的绑定
    :param request: HTTP请求对象
    :param id: OAuthUser记录ID
    :param sign: 验证签名（防止恶意请求）
    :return: 重定向到绑定成功页或403禁止访问
    """
    if not sign:  # 无签名时返回403
        return HttpResponseForbidden()
    # 验证签名合法性（使用SECRET_KEY和ID生成SHA256比对）
    if not get_sha256(settings.SECRET_KEY + str(id) + settings.SECRET_KEY).upper() == sign.upper():
        return HttpResponseForbidden()  # 签名不匹配时返回403

    # 获取对应的第三方用户记录（不存在则返回404）
    oauthuser = get_object_or_404(OAuthUser, pk=id)
    with transaction.atomic():  # 数据库事务确保操作原子性
        if oauthuser.author:  # 若已关联本地用户
            author = get_user_model().objects.get(pk=oauthuser.author_id)
        else:  # 未关联本地用户时，通过邮箱创建或查找
            result = get_user_model().objects.get_or_create(email=oauthuser.email)
            author = result[0]
            # 若为新创建的用户
            if result[1]:
                author.source = 'emailconfirm'  # 标记用户来源为邮箱验证
                # 设置用户名为第三方昵称（为空时生成默认值）
                author.username = oauthuser.nickname.strip() if oauthuser.nickname.strip() else "djangoblog" + timezone.now().strftime(
                    '%y%m%d%I%M%S')
                author.save()  # 保存本地用户
        # 关联第三方用户记录与本地用户
        oauthuser.author = author
        oauthuser.save()  # 保存第三方用户记录

    # 发送OAuth用户登录信号
    oauth_user_login_signal.send(
        sender=emailconfirm.__class__, id=oauthuser.id)
    login(request, author)  # 登录本地用户

    # 生成欢迎邮件内容（包含网站信息）
    site = 'http://' + get_current_site().domain
    content = _('''
     <p>Congratulations, you have successfully bound your email address. You can use
      %(oauthuser_type)s to directly log in to this website without a password.</p>
       You are welcome to continue to follow this site, the address is
        <a href="%(site)s" rel="bookmark">%(site)s</a>
            Thank you again!
            <br />
        If the link above cannot be opened, please copy this link to your browser.
        %(site)s
    ''') % {'oauthuser_type': oauthuser.type, 'site': site}

    # 发送绑定成功邮件
    send_email(emailto=[oauthuser.email, ], title=_('Congratulations on your successful binding!'), content=content)
    # 生成绑定成功页URL
    url = reverse('oauth:bindsuccess', kwargs={'oauthid': id})
    url = url + '?type=success'  # 添加成功标识参数
    return HttpResponseRedirect(url)  # 重定向到绑定成功页


class RequireEmailView(FormView):
    """处理邮箱填写的表单视图，继承自Django的FormView"""
    form_class = RequireEmailForm  # 指定使用的表单类
    template_name = 'oauth/require_email.html'  # 指定渲染的模板

    def get(self, request, *args, **kwargs):
        """处理GET请求：获取第三方用户记录，若已填写邮箱则跳转（注释中逻辑）"""
        oauthid = self.kwargs['oauthid']  # 从URL参数中获取第三方用户ID
        oauthuser = get_object_or_404(OAuthUser, pk=oauthid)  # 获取第三方用户记录
        if oauthuser.email:
            pass  # 若已填写邮箱，此处可添加跳转逻辑（当前注释）
        # 调用父类的GET方法，渲染表单页面
        return super(RequireEmailView, self).get(request, *args, **kwargs)

    def get_initial(self):
        """设置表单初始值：预填第三方用户ID"""
        oauthid = self.kwargs['oauthid']
        return {'email': '', 'oauthid': oauthid}  # 邮箱为空，ID为URL参数中的值

    def get_context_data(self, **kwargs):
        """扩展上下文数据：添加第三方用户的头像（用于模板显示）"""
        oauthid = self.kwargs['oauthid']
        oauthuser = get_object_or_404(OAuthUser, pk=oauthid)
        if oauthuser.picture:  # 若存在头像地址
            kwargs['picture'] = oauthuser.picture  # 添加到上下文
        # 调用父类方法，返回扩展后的上下文
        return super(RequireEmailView, self).get_context_data(**kwargs)

    def form_valid(self, form):
        """处理合法的表单提交：保存邮箱并发送验证邮件"""
        email = form.cleaned_data['email']  # 获取清洗后的邮箱
        oauthid = form.cleaned_data['oauthid']  # 获取第三方用户ID
        oauthuser = get_object_or_404(OAuthUser, pk=oauthid)  # 获取第三方用户记录
        oauthuser.email = email  # 设置邮箱
        oauthuser.save()  # 保存记录

        # 生成邮箱验证的签名
        sign = get_sha256(settings.SECRET_KEY + str(oauthuser.id) + settings.SECRET_KEY)
        # 获取当前站点域名（开发环境使用127.0.0.1:8000）
        site = get_current_site().domain
        if settings.DEBUG:
            site = '127.0.0.1:8000'
        # 生成邮箱验证链接
        path = reverse('oauth:email_confirm', kwargs={'id': oauthid, 'sign': sign})
        url = "http://{site}{path}".format(site=site, path=path)

        # 生成验证邮件内容
        content = _("""
               <p>Please click the link below to bind your email</p>

                 <a href="%(url)s" rel="bookmark">%(url)s</a>

                 Thank you again!
                 <br />
                 If the link above cannot be opened, please copy this link to your browser.
                  <br />
                 %(url)s
                """) % {'url': url}
        # 发送验证邮件
        send_email(emailto=[email, ], title=_('Bind your email'), content=content)
        # 生成绑定成功页URL（带邮件发送标识）
        url = reverse('oauth:bindsuccess', kwargs={'oauthid': oauthid})
        url = url + '?type=email'
        return HttpResponseRedirect(url)  # 重定向到绑定成功页


def bindsuccess(request, oauthid):
    """
    显示绑定成功页面（根据类型显示不同内容）
    :param request: HTTP请求对象
    :param oauthid: 第三方用户ID
    :return: 渲染绑定成功页面的响应
    """
    type = request.GET.get('type', None)  # 获取类型参数（email或success）
    oauthuser = get_object_or_404(OAuthUser, pk=oauthid)  # 获取第三方用户记录

    if type == 'email':  # 邮件已发送但未验证
        title = _('Bind your email')
        content = _(
            'Congratulations, the binding is just one step away. '
            'Please log in to your email to check the email to complete the binding. Thank you.')
    else:  # 绑定已完成
        title = _('Binding successful')
        content = _(
            "Congratulations, you have successfully bound your email address. You can use %(oauthuser_type)s"
            " to directly log in to this website without a password. You are welcome to continue to follow this site." % {
                'oauthuser_type': oauthuser.type})

    # 渲染绑定成功页面，传递标题和内容
    return render(request, 'oauth/bindsuccess.html', {'title': title, 'content': content})