import { logger } from '@goodgo/logger';
import { sign, verify, decode, SignOptions } from 'jsonwebtoken';
import { v4 as uuidv4 } from 'uuid';

import { jwtConfig } from '../../config/jwt.config';
import { cacheService } from '../../core/cache/cache.service';

/**
 * EN: JWT payload interface
 * VI: Interface cho JWT payload
 */
export interface JWTPayload {
  sub: string; // User ID
  email: string;
  roles?: string[];
  permissions?: string[];
  iat?: number;
  exp?: number;
}

/**
 * EN: Token set interface
 * VI: Interface cho bộ token
 */
export interface TokenSet {
  accessToken: string;
  refreshToken: string;
  idToken?: string;
}

/**
 * EN: JWT Service for token generation and validation
 * VI: Service JWT cho việc tạo và xác thực token
 */
export class JWTService {
  /**
   * EN: Generate access token (short-lived, 15 minutes)
   * VI: Tạo access token (sống ngắn, 15 phút)
   */
  generateAccessToken(payload: { sub: string; email: string; roles?: string[]; permissions?: string[] }): string {
    return sign(
      payload,
      jwtConfig.secret,
      {
        expiresIn: jwtConfig.expiresIn,
        issuer: jwtConfig.issuer,
        audience: jwtConfig.audience,
      } as SignOptions
    );
  }

  /**
   * EN: Generate refresh token (long-lived, 7 days)
   * VI: Tạo refresh token (sống dài, 7 ngày)
   */
  generateRefreshToken(userId: string, tokenId?: string): string {
    return sign(
      {
        sub: userId,
        tokenId: tokenId || uuidv4(),
        type: 'refresh',
      },
      jwtConfig.refreshSecret,
      {
        expiresIn: jwtConfig.refreshExpiresIn,
        issuer: jwtConfig.issuer,
      } as SignOptions
    );
  }

  /**
   * EN: Generate ID token (OIDC, 1 hour)
   * VI: Tạo ID token (OIDC, 1 giờ)
   */
  generateIdToken(payload: {
    sub: string;
    email: string;
    email_verified: boolean;
    name?: string;
    picture?: string;
    updated_at?: Date;
  }): string {
    return sign(
      {
        ...payload,
        iss: jwtConfig.issuer,
        aud: payload.sub, // ID token audience is the user
        iat: Math.floor(Date.now() / 1000),
      },
      jwtConfig.idSecret,
      {
        expiresIn: jwtConfig.idExpiresIn,
      } as SignOptions
    );
  }

  /**
   * EN: Generate complete token set
   * VI: Tạo bộ token hoàn chỉnh
   */
  async generateTokenSet(
    user: {
      id: string;
      email: string;
      emailVerified: boolean;
      username?: string;
      avatar?: string;
      updatedAt: Date;
    },
    roles: string[] = [],
    permissions: string[] = []
  ): Promise<TokenSet> {
    const tokenId = uuidv4();

    // Access Token
    const accessToken = this.generateAccessToken({
      sub: user.id,
      email: user.email,
      roles,
      permissions,
    });

    // Refresh Token
    const refreshToken = this.generateRefreshToken(user.id, tokenId);

    // ID Token (OIDC)
    const idToken = this.generateIdToken({
      sub: user.id,
      email: user.email,
      email_verified: user.emailVerified,
      name: user.username,
      picture: user.avatar,
      updated_at: user.updatedAt,
    });

    // Cache access token for fast validation
    await cacheService.set(
      cacheService.keys.token(accessToken),
      { userId: user.id, roles, permissions },
      15 * 60 // 15 minutes
    );

    return {
      accessToken,
      refreshToken,
      idToken,
    };
  }

  /**
   * EN: Verify access token
   * VI: Xác thực access token
   */
  async verifyAccessToken(token: string): Promise<JWTPayload> {
    try {
      // Check cache first
      const cached = await cacheService.get<{ userId: string; roles: string[]; permissions: string[] }>(
        cacheService.keys.token(token)
      );
      if (cached) {
        // Token is valid and cached
        return {
          sub: cached.userId,
          email: '', // Not in cache, will need to fetch
          roles: cached.roles,
          permissions: cached.permissions,
        };
      }

      // Verify JWT
      const decoded = verify(token, jwtConfig.secret, {
        issuer: jwtConfig.issuer,
        audience: jwtConfig.audience,
      }) as JWTPayload;

      return decoded;
    } catch (error) {
      logger.error('Access token verification failed', { error });
      throw new Error('Invalid or expired access token');
    }
  }

  /**
   * EN: Verify refresh token
   * VI: Xác thực refresh token
   */
  verifyRefreshToken(token: string): { sub: string; tokenId: string } {
    try {
      const decoded = verify(token, jwtConfig.refreshSecret, {
        issuer: jwtConfig.issuer,
      }) as { sub: string; tokenId: string; type: string };

      if (decoded.type !== 'refresh') {
        throw new Error('Invalid token type');
      }

      return {
        sub: decoded.sub,
        tokenId: decoded.tokenId,
      };
    } catch (error) {
      logger.error('Refresh token verification failed', { error });
      throw new Error('Invalid or expired refresh token');
    }
  }

  /**
   * EN: Verify ID token (OIDC)
   * VI: Xác thực ID token (OIDC)
   */
  verifyIdToken(token: string, expectedAudience: string): unknown {
    try {
      const decoded = verify(token, jwtConfig.idSecret, {
        issuer: jwtConfig.issuer,
        audience: expectedAudience,
      });

      return decoded;
    } catch (error) {
      logger.error('ID token verification failed', { error });
      throw new Error('Invalid or expired ID token');
    }
  }

  /**
   * EN: Decode token without verification (for debugging)
   * VI: Giải mã token không xác thực (để debug)
   */
  decodeToken(token: string): unknown {
    return decode(token);
  }

  /**
   * EN: Invalidate token (remove from cache)
   * VI: Làm mất hiệu lực token (xóa khỏi cache)
   */
  async invalidateToken(token: string): Promise<void> {
    await cacheService.del(cacheService.keys.token(token));
  }
}

export const jwtService = new JWTService();
