<?php

declare(strict_types=1);

namespace App\Controller\Api\V1;

use App\Controller\AbstractController;
use App\Middleware\AuthMiddleware;
use App\Model\User;
use App\Service\OperationLogService;
use App\Utils\RequestHelper;
use Carbon\Carbon;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\Middleware;
use Hyperf\HttpServer\Annotation\RequestMapping;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;
use OpenApi\Attributes as OA;
use Qbhy\HyperfAuth\AuthManager;

#[OA\Tag(name: 'Auth', description: '认证与个人信息')]
#[Controller(prefix: "/api/v1")]
class AuthController extends AbstractController
{
    /**
     * 用户注册
     */
    #[OA\Post(
        path: '/register',
        summary: '用户注册',
        description: '注册新用户，需提供用户名、密码和邮箱',
        tags: ['Auth'],
        requestBody: new OA\RequestBody(
            required: true,
            content: new OA\JsonContent(
                required: ['username', 'password', 'email'],
                properties: [
                    new OA\Property(property: 'username', type: 'string', minLength: 3, maxLength: 20, example: 'new_user'),
                    new OA\Property(property: 'password', type: 'string', minLength: 6, maxLength: 32, example: 'Pass_1234'),
                    new OA\Property(property: 'email', type: 'string', format: 'email', maxLength: 100, example: 'user@example.com'),
                ]
            )
        ),
        responses: [
            new OA\Response(
                response: 200,
                description: '注册成功',
                content: new OA\JsonContent(properties: [
                    new OA\Property(property: 'code', type: 'integer', example: 0),
                    new OA\Property(property: 'message', type: 'string', example: '注册成功'),
                    new OA\Property(property: 'data', properties: [
                        new OA\Property(property: 'id', type: 'integer', example: 1),
                        new OA\Property(property: 'username', type: 'string', example: 'new_user'),
                        new OA\Property(property: 'email', type: 'string', example: 'user@example.com'),
                    ], type: 'object'),
                ])
            ),
            new OA\Response(response: 400, description: '参数校验失败或唯一性冲突', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
        ]
    )]
    #[RequestMapping(path: "register", methods: "POST")]
    public function register(RequestInterface $request, ResponseInterface $response): \Psr\Http\Message\ResponseInterface|array
    {
        $username = $request->input('username');
        $password = $request->input('password');
        $email = $request->input('email');

        // 校验 username
        if (!is_string($username) || trim($username) === '') {
            return $response->json([
                'code' => 400,
                'message' => '用户名不能为空',
            ])->withStatus(400);
        }

        $username = trim($username);
        $username_length = strlen($username);
        if ($username_length < 3 || $username_length > 20) {
            return $response->json([
                'code' => 400,
                'message' => '用户名长度需在 3-20 个字符',
            ])->withStatus(400);
        }

        // 校验 password
        if (!is_string($password) || $password === '') {
            return $response->json([
                'code' => 400,
                'message' => '密码不能为空',
            ])->withStatus(400);
        }

        $password_length = strlen($password);
        if ($password_length < 6 || $password_length > 32) {
            return $response->json([
                'code' => 400,
                'message' => '密码长度需在 6-32 个字符',
            ])->withStatus(400);
        }

        // 校验 email
        if (!is_string($email) || trim($email) === '') {
            return $response->json([
                'code' => 400,
                'message' => '邮箱不能为空',
            ])->withStatus(400);
        }

        $email = trim($email);
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return $response->json([
                'code' => 400,
                'message' => '邮箱格式不正确',
            ])->withStatus(400);
        }

        if (strlen($email) > 100) {
            return $response->json([
                'code' => 400,
                'message' => '邮箱长度不能超过 100 个字符',
            ])->withStatus(400);
        }

        // 唯一性检查
        if (User::query()->where('username', $username)->exists()) {
            return $response->json([
                'code' => 400,
                'message' => '用户名已存在',
            ])->withStatus(400);
        }

        if (User::query()->where('email', $email)->exists()) {
            return $response->json([
                'code' => 400,
                'message' => '邮箱已被注册',
            ])->withStatus(400);
        }

        // 创建用户
        $user = User::create([
            'username' => $username,
            'password' => $password,
            'email' => $email,
            'status' => 1,
        ]);

        return [
            'code' => 0,
            'message' => '注册成功',
            'data' => [
                'id' => $user->id,
                'username' => $user->username,
                'email' => $user->email,
            ],
        ];
    }

    /**
     * 用户登录
     */
    #[OA\Post(
        path: '/login',
        summary: '用户登录',
        description: '使用用户名和密码登录，返回 access_token 和 refresh_token',
        tags: ['Auth'],
        requestBody: new OA\RequestBody(
            required: true,
            content: new OA\JsonContent(
                required: ['username', 'password'],
                properties: [
                    new OA\Property(property: 'username', type: 'string', example: 'admin'),
                    new OA\Property(property: 'password', type: 'string', example: 'Pass_1234'),
                ]
            )
        ),
        responses: [
            new OA\Response(
                response: 200,
                description: '登录成功',
                content: new OA\JsonContent(properties: [
                    new OA\Property(property: 'code', type: 'integer', example: 0),
                    new OA\Property(property: 'message', type: 'string', example: '登录成功'),
                    new OA\Property(property: 'data', properties: [
                        new OA\Property(property: 'access_token', type: 'string'),
                        new OA\Property(property: 'refresh_token', type: 'string'),
                        new OA\Property(property: 'token_type', type: 'string', example: 'Bearer'),
                        new OA\Property(property: 'expires_in', type: 'integer', example: 7200),
                        new OA\Property(property: 'user', properties: [
                            new OA\Property(property: 'id', type: 'integer'),
                            new OA\Property(property: 'username', type: 'string'),
                            new OA\Property(property: 'email', type: 'string'),
                            new OA\Property(property: 'role', type: 'string', example: 'administrator'),
                        ], type: 'object'),
                    ], type: 'object'),
                ])
            ),
            new OA\Response(response: 400, description: '参数校验失败', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
            new OA\Response(response: 401, description: '用户名或密码错误', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
            new OA\Response(response: 403, description: '账号已被禁用', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
        ]
    )]
    #[RequestMapping(path: "login", methods: "POST")]
    public function login(RequestInterface $request, ResponseInterface $response, AuthManager $auth): \Psr\Http\Message\ResponseInterface|array
    {
        $username = $request->input('username');
        $password = $request->input('password');

        // 校验参数
        if (!is_string($username) || trim($username) === '') {
            return $response->json([
                'code' => 400,
                'message' => '用户名不能为空',
            ])->withStatus(400);
        }

        if (!is_string($password) || $password === '') {
            return $response->json([
                'code' => 400,
                'message' => '密码不能为空',
            ])->withStatus(400);
        }

        // 查找用户
        $user = User::query()->where('username', trim($username))->first();

        if (!$user) {
            return $response->json([
                'code' => 401,
                'message' => '用户名或密码错误',
            ])->withStatus(401);
        }

        // 验证密码
        if (!$user->verifyPassword($password)) {
            return $response->json([
                'code' => 401,
                'message' => '用户名或密码错误',
            ])->withStatus(401);
        }

        // 检查用户状态
        if ($user->status !== 1) {
            return $response->json([
                'code' => 403,
                'message' => '账号已被禁用',
            ])->withStatus(403);
        }

        $user->load('role');

        // 生成 Access Token（注入 role 到 JWT payload，前端从 JWT 解码 role 作为可信权限来源）
        $token = $auth->guard('jwt')->login($user, ['role' => $user->role?->name ?? 'accessor']);

        // 生成 Refresh Token
        $refreshToken = bin2hex(random_bytes(32));
        $user->refresh_token = $refreshToken;
        $user->refresh_token_expires_at = Carbon::now()->addDays(30);
        $user->save();

        OperationLogService::log(
            user_id: $user->id,
            action: 'auth.login',
            target_type: 'user',
            target_id: $user->id,
            description: "用户 {$user->username} 登录",
            ip: RequestHelper::getClientIp($this->request),
        );

        return [
            'code' => 0,
            'message' => '登录成功',
            'data' => [
                'access_token' => $token,
                'refresh_token' => $refreshToken,
                'token_type' => 'Bearer',
                'expires_in' => 7200,
                'user' => [
                    'id' => $user->id,
                    'username' => $user->username,
                    'email' => $user->email,
                    'role' => $user->role?->name ?? 'accessor',
                ],
            ],
        ];
    }

    /**
     * 刷新 Access Token
     */
    #[OA\Get(
        path: '/refresh',
        summary: '刷新 Access Token',
        description: '使用 refresh_token 获取新的 access_token，同时轮换 refresh_token',
        security: [['bearerAuth' => []]],
        tags: ['Auth'],
        parameters: [
            new OA\Parameter(name: 'refresh_token', in: 'query', required: true, description: 'Refresh Token', schema: new OA\Schema(type: 'string')),
        ],
        responses: [
            new OA\Response(
                response: 200,
                description: 'Token 刷新成功',
                content: new OA\JsonContent(properties: [
                    new OA\Property(property: 'code', type: 'integer', example: 0),
                    new OA\Property(property: 'message', type: 'string', example: 'Token 刷新成功'),
                    new OA\Property(property: 'data', properties: [
                        new OA\Property(property: 'access_token', type: 'string'),
                        new OA\Property(property: 'refresh_token', type: 'string'),
                        new OA\Property(property: 'token_type', type: 'string', example: 'Bearer'),
                        new OA\Property(property: 'expires_in', type: 'integer', example: 7200),
                    ], type: 'object'),
                ])
            ),
            new OA\Response(response: 400, description: '缺少 refresh_token 参数', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
            new OA\Response(response: 401, description: 'refresh_token 无效或已过期', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
            new OA\Response(response: 403, description: '账号已被禁用', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
        ]
    )]
    #[RequestMapping(path: "refresh", methods: "GET")]
    #[Middleware(AuthMiddleware::class)]
    public function refresh(RequestInterface $request, ResponseInterface $response, AuthManager $auth): \Psr\Http\Message\ResponseInterface|array
    {
        $refreshToken = $request->input('refresh_token');

        if (!is_string($refreshToken) || $refreshToken === '') {
            return $response->json([
                'code' => 400,
                'message' => '缺少 refresh_token 参数',
            ])->withStatus(400);
        }

        // 查找用户
        $user = User::query()->where('refresh_token', $refreshToken)->first();

        if (!$user) {
            return $response->json([
                'code' => 401,
                'message' => '无效的 refresh_token',
            ])->withStatus(401);
        }

        // 验证 refresh token 是否过期
        if (!$user->isRefreshTokenValid()) {
            return $response->json([
                'code' => 401,
                'message' => 'refresh_token 已过期，请重新登录',
            ])->withStatus(401);
        }

        // 检查用户状态
        if ($user->status !== 1) {
            return $response->json([
                'code' => 403,
                'message' => '账号已被禁用',
            ])->withStatus(403);
        }

        // 生成新的 Access Token（注入 role 到 JWT payload）
        $user->load('role');
        $token = $auth->guard('jwt')->login($user, ['role' => $user->role?->name ?? 'accessor']);

        // 生成新的 Refresh Token（轮换以提升安全性）
        $newRefreshToken = bin2hex(random_bytes(32));
        $user->refresh_token = $newRefreshToken;
        $user->refresh_token_expires_at = Carbon::now()->addDays(30);
        $user->save();

        return [
            'code' => 0,
            'message' => 'Token 刷新成功',
            'data' => [
                'access_token' => $token,
                'refresh_token' => $newRefreshToken,
                'token_type' => 'Bearer',
                'expires_in' => 7200,
            ],
        ];
    }

    /**
     * 获取当前用户信息
     */
    #[OA\Get(
        path: '/me',
        summary: '获取当前用户信息',
        security: [['bearerAuth' => []]],
        tags: ['Auth'],
        responses: [
            new OA\Response(
                response: 200,
                description: '获取成功',
                content: new OA\JsonContent(properties: [
                    new OA\Property(property: 'code', type: 'integer', example: 0),
                    new OA\Property(property: 'message', type: 'string', example: '获取成功'),
                    new OA\Property(property: 'data', properties: [
                        new OA\Property(property: 'id', type: 'integer', example: 1),
                        new OA\Property(property: 'username', type: 'string', example: 'admin'),
                        new OA\Property(property: 'email', type: 'string', example: 'admin@example.com'),
                        new OA\Property(property: 'status', type: 'integer', example: 1),
                        new OA\Property(property: 'api_key_enabled', type: 'boolean', example: true, description: 'API Key 功能是否启用'),
                        new OA\Property(property: 'ext', type: 'object', nullable: true),
                        new OA\Property(property: 'created_at', type: 'string', format: 'date-time'),
                    ], type: 'object'),
                ])
            ),
            new OA\Response(response: 401, description: '未认证', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
        ]
    )]
    #[RequestMapping(path: "me", methods: "GET")]
    #[Middleware(AuthMiddleware::class)]
    public function me(ResponseInterface $response): \Psr\Http\Message\ResponseInterface|array
    {
        $user = $this->getAuthUser();

        if (!$user) {
            return $response->json([
                'code' => 401,
                'message' => '未授权',
            ])->withStatus(401);
        }

        $user->load('role');

        return [
            'code' => 0,
            'message' => '获取成功',
            'data' => [
                'id' => $user->id,
                'username' => $user->username,
                'email' => $user->email,
                'role' => $user->role?->name ?? 'accessor',
                'status' => $user->status,
                'api_key_enabled' => $user->api_key_enabled,
                'ext' => $user->ext,
                'created_at' => $user->created_at->toDateTimeString(),
            ],
        ];
    }

    /**
     * 更新个人信息
     *
     * 当前用户更新自己的 email 和 ext 字段
     */
    #[OA\Put(
        path: '/me/profile',
        summary: '更新个人信息',
        description: '当前用户更新自己的 email 和 ext 字段',
        security: [['bearerAuth' => []]],
        tags: ['Auth'],
        requestBody: new OA\RequestBody(
            required: true,
            content: new OA\JsonContent(properties: [
                new OA\Property(property: 'email', type: 'string', format: 'email', maxLength: 100, example: 'new@example.com'),
                new OA\Property(property: 'ext', type: 'object', nullable: true, example: ['nickname' => 'user']),
            ])
        ),
        responses: [
            new OA\Response(
                response: 200,
                description: '更新成功',
                content: new OA\JsonContent(properties: [
                    new OA\Property(property: 'code', type: 'integer', example: 0),
                    new OA\Property(property: 'message', type: 'string', example: '个人信息更新成功'),
                    new OA\Property(property: 'data', properties: [
                        new OA\Property(property: 'id', type: 'integer'),
                        new OA\Property(property: 'username', type: 'string'),
                        new OA\Property(property: 'email', type: 'string'),
                        new OA\Property(property: 'status', type: 'integer'),
                        new OA\Property(property: 'ext', type: 'object', nullable: true),
                        new OA\Property(property: 'created_at', type: 'string', format: 'date-time'),
                    ], type: 'object'),
                ])
            ),
            new OA\Response(response: 400, description: '参数校验失败或唯一性冲突', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
            new OA\Response(response: 401, description: '未认证', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
        ]
    )]
    #[RequestMapping(path: "me/profile", methods: "PUT")]
    #[Middleware(AuthMiddleware::class)]
    public function updateProfile(RequestInterface $request, ResponseInterface $response): \Psr\Http\Message\ResponseInterface|array
    {
        $user = $this->getAuthUser();

        if (!$user) {
            return $response->json([
                'code' => 401,
                'message' => '未授权',
            ])->withStatus(401);
        }

        $email = $request->input('email');
        $ext = $request->input('ext');
        $updates = [];

        if ($email !== null) {
            if (!is_string($email) || trim($email) === '') {
                return $response->json([
                    'code' => 400,
                    'message' => '邮箱不能为空',
                ])->withStatus(400);
            }

            $email = trim($email);
            if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
                return $response->json([
                    'code' => 400,
                    'message' => '邮箱格式不正确',
                ])->withStatus(400);
            }

            if (strlen($email) > 100) {
                return $response->json([
                    'code' => 400,
                    'message' => '邮箱长度不能超过 100 个字符',
                ])->withStatus(400);
            }

            if (User::query()->where('email', $email)->where('id', '!=', $user->id)->exists()) {
                return $response->json([
                    'code' => 400,
                    'message' => '邮箱已被注册',
                ])->withStatus(400);
            }

            $updates['email'] = $email;
        }

        if ($ext !== null) {
            if (!is_array($ext)) {
                return $response->json([
                    'code' => 400,
                    'message' => 'ext 必须为对象',
                ])->withStatus(400);
            }

            $updates['ext'] = $ext;
        }

        if ($updates === []) {
            return $response->json([
                'code' => 400,
                'message' => '缺少可更新字段',
            ])->withStatus(400);
        }

        $user->fill($updates);
        $user->save();
        $user->refresh();

        return [
            'code' => 0,
            'message' => '个人信息更新成功',
            'data' => [
                'id' => $user->id,
                'username' => $user->username,
                'email' => $user->email,
                'status' => $user->status,
                'ext' => $user->ext,
                'created_at' => $user->created_at->toDateTimeString(),
            ],
        ];
    }

    /**
     * 修改密码
     *
     * 需验证旧密码，修改成功后清除 refresh_token 强制重新登录
     */
    #[OA\Put(
        path: '/me/password',
        summary: '修改密码',
        description: '修改当前用户密码，需验证旧密码。修改成功后清除 refresh_token，需重新登录',
        security: [['bearerAuth' => []]],
        tags: ['Auth'],
        requestBody: new OA\RequestBody(
            required: true,
            content: new OA\JsonContent(
                required: ['old_password', 'new_password'],
                properties: [
                    new OA\Property(property: 'old_password', type: 'string', example: 'OldPass_1234'),
                    new OA\Property(property: 'new_password', type: 'string', minLength: 6, maxLength: 32, example: 'NewPass_5678'),
                ]
            )
        ),
        responses: [
            new OA\Response(
                response: 200,
                description: '密码修改成功',
                content: new OA\JsonContent(properties: [
                    new OA\Property(property: 'code', type: 'integer', example: 0),
                    new OA\Property(property: 'message', type: 'string', example: '密码修改成功，请重新登录'),
                ])
            ),
            new OA\Response(response: 400, description: '参数校验失败或旧密码不正确', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
            new OA\Response(response: 401, description: '未认证', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
        ]
    )]
    #[RequestMapping(path: "me/password", methods: "PUT")]
    #[Middleware(AuthMiddleware::class)]
    public function changePassword(RequestInterface $request, ResponseInterface $response): \Psr\Http\Message\ResponseInterface|array
    {
        $user = $this->getAuthUser();

        if (!$user) {
            return $response->json([
                'code' => 401,
                'message' => '未授权',
            ])->withStatus(401);
        }

        $old_password = $request->input('old_password');
        $new_password = $request->input('new_password');

        // 验证旧密码
        if (!is_string($old_password) || $old_password === '') {
            return $response->json([
                'code' => 400,
                'message' => '旧密码不能为空',
            ])->withStatus(400);
        }

        if (!$user->verifyPassword($old_password)) {
            return $response->json([
                'code' => 400,
                'message' => '旧密码不正确',
            ])->withStatus(400);
        }

        // 验证新密码
        if (!is_string($new_password) || $new_password === '') {
            return $response->json([
                'code' => 400,
                'message' => '新密码不能为空',
            ])->withStatus(400);
        }

        $new_password_length = strlen($new_password);
        if ($new_password_length < 6 || $new_password_length > 32) {
            return $response->json([
                'code' => 400,
                'message' => '新密码长度需在 6-32 个字符',
            ])->withStatus(400);
        }

        // 修改密码并清除 refresh_token
        $user->password = $new_password;
        $user->refresh_token = null;
        $user->refresh_token_expires_at = null;
        $user->save();

        OperationLogService::log(
            user_id: $user->id,
            action: 'user.password_change',
            target_type: 'user',
            target_id: $user->id,
            description: "用户 {$user->username} 修改密码",
            ip: RequestHelper::getClientIp($this->request),
        );

        return [
            'code' => 0,
            'message' => '密码修改成功，请重新登录',
        ];
    }

    /**
     * 退出登录
     */
    #[OA\Get(
        path: '/logout',
        summary: '退出登录',
        description: '退出登录，清除 refresh_token 并注销当前 JWT token',
        security: [['bearerAuth' => []]],
        tags: ['Auth'],
        responses: [
            new OA\Response(
                response: 200,
                description: '退出成功',
                content: new OA\JsonContent(properties: [
                    new OA\Property(property: 'code', type: 'integer', example: 0),
                    new OA\Property(property: 'message', type: 'string', example: '退出成功'),
                ])
            ),
            new OA\Response(response: 401, description: '未认证', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
        ]
    )]
    #[RequestMapping(path: "logout", methods: "GET")]
    #[Middleware(AuthMiddleware::class)]
    public function logout(AuthManager $auth): array
    {
        $user = $this->getAuthUser();

        if ($user) {
            OperationLogService::log(
                user_id: $user->id,
                action: 'auth.logout',
                target_type: 'user',
                target_id: $user->id,
                description: "用户 {$user->username} 退出登录",
                ip: RequestHelper::getClientIp($this->request),
            );

            // 清除 refresh token
            $user->refresh_token = null;
            $user->refresh_token_expires_at = null;
            $user->save();
        }

        // 注销当前 token
        $auth->guard('jwt')->logout();

        return [
            'code' => 0,
            'message' => '退出成功',
        ];
    }
}
