import crypto from 'crypto';

import { logger } from '@goodgo/logger';
import { PrismaClient } from '@prisma/client';

import { getPrismaClient } from '../../config/database.config';
import { cacheService } from '../../core/cache/cache.service';
import { OIDCDiscoveryDocument, JWKSResponse } from '../../types/oidc.types';
import { jwtService } from '../token/jwt.service';


/**
 * EN: OIDC Provider Service (simplified implementation)
 * VI: Service OIDC Provider (triển khai đơn giản)
 */
export class OIDCProviderService {
  private readonly issuer = process.env.OIDC_ISSUER || 'http://localhost:4000';
  private prisma: PrismaClient;

  constructor() {
    this.prisma = getPrismaClient();
  }

  /**
   * EN: Get OIDC discovery document
   * VI: Lấy tài liệu khám phá OIDC
   */
  getDiscoveryDocument(): OIDCDiscoveryDocument {
    return {
      issuer: this.issuer,
      authorization_endpoint: `${this.issuer}/oidc/authorize`,
      token_endpoint: `${this.issuer}/oidc/token`,
      userinfo_endpoint: `${this.issuer}/oidc/userinfo`,
      jwks_uri: `${this.issuer}/oidc/jwks`,
      scopes_supported: ['openid', 'email', 'profile', 'offline_access'],
      response_types_supported: ['code', 'token', 'id_token'],
      grant_types_supported: [
        'authorization_code',
        'implicit',
        'refresh_token',
        'client_credentials',
      ],
      subject_types_supported: ['public'],
      id_token_signing_alg_values_supported: ['RS256', 'ES256'],
      token_endpoint_auth_methods_supported: [
        'client_secret_basic',
        'client_secret_post',
        'client_secret_jwt',
      ],
    };
  }

  /**
   * EN: Generate authorization code
   * VI: Tạo authorization code
   */
  async generateAuthorizationCode(
    clientId: string,
    userId: string,
    redirectUri: string,
    scopes: string[]
  ): Promise<string> {
    const code = crypto.randomBytes(32).toString('hex');
    
    // Store code in cache with TTL (10 minutes)
    const codeData = {
      clientId,
      userId,
      redirectUri,
      scopes,
      createdAt: new Date().toISOString(),
    };
    await cacheService.set(`oidc:code:${code}`, codeData, 600); // 10 minutes
    
    logger.debug('Authorization code generated', { clientId, userId });
    
    return code;
  }

  /**
   * EN: Exchange authorization code for tokens
   * VI: Đổi authorization code lấy tokens
   */
  async exchangeCodeForTokens(
    code: string,
    clientId: string,
    clientSecret: string,
    redirectUri: string
  ): Promise<{ accessToken: string; refreshToken: string; idToken: string }> {
    // Validate authorization code
    const codeData = await cacheService.get<{
      clientId: string;
      userId: string;
      redirectUri: string;
      scopes: string[];
      createdAt: string;
    }>(`oidc:code:${code}`);

    if (!codeData) {
      throw new Error('Invalid or expired authorization code');
    }

    // Validate client and redirect URI
    if (codeData.clientId !== clientId) {
      throw new Error('Invalid client ID');
    }

    if (codeData.redirectUri !== redirectUri) {
      throw new Error('Invalid redirect URI');
    }

    // Get user data
    const user = await this.prisma.user.findUnique({
      where: { id: codeData.userId },
    });

    if (!user) {
      throw new Error('User not found');
    }

    // Get user roles and permissions (simplified - in production, use RBAC service)
    const roles: string[] = [];
    const permissions: string[] = [];

    // Generate tokens with actual user data
    const tokens = await jwtService.generateTokenSet(
      {
        id: user.id,
        email: user.email,
        emailVerified: user.emailVerified,
        username: user.username || undefined,
        updatedAt: user.updatedAt,
      },
      roles,
      permissions
    );

    // Delete used authorization code
    await cacheService.del(`oidc:code:${code}`);

    return {
      accessToken: tokens.accessToken,
      refreshToken: tokens.refreshToken,
      idToken: tokens.idToken || '',
    };
  }

  /**
   * EN: Get JWKS (JSON Web Key Set)
   * VI: Lấy JWKS
   */
  getJWKS(): JWKSResponse {
    // In production, return actual public keys
    return {
      keys: [
        {
          kty: 'RSA',
          use: 'sig',
          kid: '1',
          // Public key would go here
        },
      ],
    };
  }
}

export const oidcProviderService = new OIDCProviderService();
