"""认证服务实现。"""

from __future__ import annotations

from datetime import datetime, timezone
from typing import Any

from fastapi_common.fastapi_common_logger import logger
from fastapi_common.fastapi_common_security.jwtService import JwtService
from fastapi_common.fastapi_common_sqlalchemy.database import GetAsyncSession
from fastapi_common.fastapi_common_web.domain.responses import StatusCodeEnum
from fastapi_common.fastapi_common_web.exception.LeauditException import LeauditException

from fastapi_modules.fastapi_leaudit.domian.vo.auth.loginTokenVo import LoginTokenVO
from fastapi_modules.fastapi_leaudit.services.authService import IAuthService


class AuthServiceImpl(IAuthService):
    """认证服务实现。"""

    @staticmethod
    def _naive_utcnow() -> datetime:
        """返回适配 timestamp without time zone 的 UTC 时间。"""
        return datetime.utcnow()

    async def PasswordLogin(self, Sub: str, Password: str) -> LoginTokenVO:
        """账密登录。

        现阶段仍兼容旧库明文密码，后续应迁移到哈希校验。
        登录标识同时兼容旧系统常见的 `sub` 与 `username`，
        避免前端展示用户名为 `admin`、实际登录只能输入 `000`。
        """
        async with GetAsyncSession() as session:
            from sqlalchemy import text

            result = await session.execute(
                text(
                    "SELECT id, sub, username, nick_name, phone_number, email, "
                    "ou_id, ou_name, is_leader, password, status, deleted_at, "
                    "try_count, try_login_time, area, tenant_name, dep_name, dep_short_name "
                    "FROM sso_users "
                    "WHERE deleted_at IS NULL AND (sub = :identifier OR username = :identifier) "
                    "ORDER BY CASE WHEN sub = :identifier THEN 0 ELSE 1 END, id ASC "
                    "LIMIT 1"
                ),
                {"identifier": Sub},
            )
            row = result.fetchone()

            if not row:
                logger.warning("登录失败: 用户不存在 - identifier=%s", Sub)
                raise LeauditException(StatusCodeEnum.HTTP_401_UNAUTHORIZED, "账号或密码错误")

            user = dict(row._mapping)

            if user.get("deleted_at") is not None:
                logger.warning("登录失败: 账号已删除 - sub=%s", Sub)
                raise LeauditException(StatusCodeEnum.HTTP_401_UNAUTHORIZED, "账号或密码错误")

            if user.get("status") != 0:
                logger.warning("登录失败: 账号已禁用 - sub=%s, status=%s", Sub, user.get("status"))
                raise LeauditException(StatusCodeEnum.HTTP_401_UNAUTHORIZED, "账号或密码错误")

            if user.get("password") != Password:
                logger.warning("登录失败: 密码错误 - sub=%s", Sub)
                raise LeauditException(StatusCodeEnum.HTTP_401_UNAUTHORIZED, "账号或密码错误")

            await self._ensure_default_role(session, user["id"])
            return await self._buildLoginResponse(user, session)

    async def OAuthLogin(
        self,
        Sub: str,
        Username: str | None,
        Nickname: str | None,
        Email: str | None,
        PhoneNumber: str | None,
        OuId: str | None,
        OuName: str | None,
        IsLeader: bool | None,
        Area: str | None,
        ExpiresIn: int,
    ) -> LoginTokenVO:
        """OAuth 登录。

        当前阶段 area 不能被前端登录请求直接覆盖。
        如果用户不存在，则仅创建基础账号信息，地区字段留待可信后台来源补齐。
        """
        del Area, ExpiresIn

        async with GetAsyncSession() as session:
            from sqlalchemy import text

            result = await session.execute(
                text(
                    "SELECT id, sub, username, nick_name, phone_number, email, "
                    "ou_id, ou_name, is_leader, status, deleted_at, area, "
                    "tenant_name, dep_name, dep_short_name "
                    "FROM sso_users WHERE sub = :sub"
                ),
                {"sub": Sub},
            )
            row = result.fetchone()

            if row:
                user = dict(row._mapping)
                if user.get("deleted_at") is not None or user.get("status") != 0:
                    raise LeauditException(StatusCodeEnum.HTTP_401_UNAUTHORIZED, "账号已被禁用或删除")

                await session.execute(
                    text(
                        "UPDATE sso_users SET username = :username, nick_name = :nick, "
                        "email = :email, phone_number = :phone, ou_id = :ou_id, "
                        "ou_name = :ou_name, is_leader = :is_leader, "
                        "updated_at = :now WHERE id = :id"
                    ),
                    {
                        "username": Username or user.get("username") or Sub,
                        "nick": Nickname or user.get("nick_name") or Username or Sub,
                        "email": Email,
                        "phone": PhoneNumber,
                        "ou_id": OuId or user.get("ou_id") or "",
                        "ou_name": OuName or user.get("ou_name") or "",
                        "is_leader": IsLeader if IsLeader is not None else user.get("is_leader"),
                        "now": self._naive_utcnow(),
                        "id": user["id"],
                    },
                )
            else:
                created = await session.execute(
                    text(
                        "INSERT INTO sso_users (sub, username, nick_name, email, phone_number, "
                        "ou_id, ou_name, is_leader, status, created_at, updated_at) "
                        "VALUES (:sub, :username, :nick, :email, :phone, :ou_id, "
                        ":ou_name, :is_leader, 0, :now, :now) RETURNING id"
                    ),
                    {
                        "sub": Sub,
                        "username": Username or Sub,
                        "nick": Nickname or Username or Sub,
                        "email": Email,
                        "phone": PhoneNumber,
                        "ou_id": OuId or "",
                        "ou_name": OuName or "",
                        "is_leader": bool(IsLeader),
                        "now": self._naive_utcnow(),
                    },
                )
                user_id = created.scalar_one()
                await self._ensure_default_role(session, user_id)

            result = await session.execute(
                text(
                    "SELECT id, sub, username, nick_name, phone_number, email, "
                    "ou_id, ou_name, is_leader, area, tenant_name, dep_name, dep_short_name "
                    "FROM sso_users WHERE sub = :sub"
                ),
                {"sub": Sub},
            )
            row = result.fetchone()
            user = dict(row._mapping) if row else {}

            if not user:
                raise LeauditException(StatusCodeEnum.HTTP_500_INTERNAL_SERVER_ERROR, "用户创建失败")

            await self._ensure_default_role(session, user["id"])
            return await self._buildLoginResponse(user, session)

    async def GetCurrentUser(self, UserId: int) -> dict:
        """获取当前登录用户信息。"""
        async with GetAsyncSession() as session:
            from sqlalchemy import text

            result = await session.execute(
                text(
                    "SELECT id, sub, username, nick_name, phone_number, email, "
                    "ou_id, ou_name, is_leader, status, deleted_at, area, "
                    "tenant_name, dep_name, dep_short_name "
                    "FROM sso_users WHERE id = :uid"
                ),
                {"uid": UserId},
            )
            row = result.fetchone()
            if not row:
                raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "用户不存在")

            user = dict(row._mapping)
            if user.get("deleted_at") is not None or user.get("status") != 0:
                raise LeauditException(StatusCodeEnum.HTTP_401_UNAUTHORIZED, "账号已被禁用或删除")

            await self._ensure_default_role(session, user["id"])
            identity = await self._loadUserIdentity(session, user["id"])
            return self._buildUserInfo(user, identity)

    async def _buildLoginResponse(self, user: dict[str, Any], session) -> LoginTokenVO:
        """组装登录响应：查询角色/权限 → 签发 JWT。"""
        identity = await self._loadUserIdentity(session, user["id"])
        user_info = self._buildUserInfo(user, identity)

        tokens = JwtService.generate(
            userId=user["id"],
            username=user.get("username") or user.get("sub", ""),
            nickName=user.get("nick_name") or "",
            ouId=user.get("ou_id") or "",
            ouName=user.get("ou_name") or "",
            roles=identity["roles"],
            permissions=identity["permissions"],
            area=user.get("area"),
            userRole=identity["primary_role"],
        )

        return LoginTokenVO(
            access_token=tokens["access_token"],
            token_type="Bearer",
            expires_in=tokens["expires_in"],
            issued_time=tokens.get("issued_time", ""),
            user_info=user_info,
        )

    async def _ensure_default_role(self, session, user_id: int) -> None:
        """确保用户至少拥有一个默认 common 角色。"""
        from sqlalchemy import text

        role_count = await session.execute(
            text("SELECT COUNT(*) FROM user_role WHERE user_id = :uid"),
            {"uid": user_id},
        )
        if (role_count.scalar_one() or 0) > 0:
            return

        common_role = await session.execute(
            text("SELECT id FROM roles WHERE role_key = 'common' LIMIT 1")
        )
        common_role_id = common_role.scalar_one_or_none()
        if common_role_id is None:
            logger.warning("默认角色 common 不存在，无法为用户 %s 自动分配角色", user_id)
            return

        await session.execute(
            text(
                "INSERT INTO user_role (user_id, role_id, created_at, updated_at) "
                "VALUES (:uid, :rid, :now, :now) "
                "ON CONFLICT (user_id, role_id) DO NOTHING"
            ),
            {"uid": user_id, "rid": common_role_id, "now": datetime.now(timezone.utc)},
        )
        logger.info("已为用户 %s 自动补默认角色 common", user_id)

    async def _loadUserIdentity(self, session, user_id: int) -> dict[str, Any]:
        """加载用户角色和权限聚合结果。"""
        from sqlalchemy import text

        role_rows = await session.execute(
            text(
                "SELECT r.role_key, COALESCE(r.priority, 0) AS priority "
                "FROM user_role ur "
                "JOIN roles r ON ur.role_id = r.id "
                "WHERE ur.user_id = :uid "
                "ORDER BY COALESCE(r.priority, 0) DESC, r.id ASC"
            ),
            {"uid": user_id},
        )
        roles = [row[0] for row in role_rows.fetchall()]
        if not roles:
            roles = ["common"]

        perm_rows = await session.execute(
            text(
                "SELECT p.permission_key, rp.grant_type "
                "FROM user_role ur "
                "JOIN roles r ON ur.role_id = r.id "
                "JOIN role_permissions rp ON r.id = rp.role_id "
                "JOIN permissions p ON rp.permission_id = p.id "
                "WHERE ur.user_id = :uid"
            ),
            {"uid": user_id},
        )

        grants: set[str] = set()
        denies: set[str] = set()
        for permission_key, grant_type in perm_rows.fetchall():
            if grant_type == "DENY":
                denies.add(permission_key)
            else:
                grants.add(permission_key)

        permissions = sorted(grants - denies)
        return {
            "roles": roles,
            "primary_role": roles[0],
            "permissions": permissions,
        }

    @staticmethod
    def _buildUserInfo(user: dict[str, Any], identity: dict[str, Any]) -> dict[str, Any]:
        """组装统一用户信息。"""
        return {
            "user_id": user["id"],
            "sub": user.get("sub"),
            "username": user.get("username"),
            "nick_name": user.get("nick_name"),
            "email": user.get("email"),
            "phone_number": user.get("phone_number"),
            "ou_id": user.get("ou_id"),
            "ou_name": user.get("ou_name"),
            "is_leader": user.get("is_leader"),
            "area": user.get("area"),
            "user_role": identity["primary_role"],
            "roles": identity["roles"],
            "permissions": identity["permissions"],
            "tenant_name": user.get("tenant_name"),
            "dep_name": user.get("dep_name"),
            "dep_short_name": user.get("dep_short_name"),
        }
