import axios, { AxiosError } from 'axios';
import * as jose from 'jose';
import authentikConfig from '../config/authentik';
import logger from '../utils/logger';
import { TokenResponse, UserInfo } from '../types/auth.types';

class AuthentikService {
  private jwks: ReturnType<typeof jose.createRemoteJWKSet> | null = null;

  private getJwks(): ReturnType<typeof jose.createRemoteJWKSet> {
    if (!this.jwks) {
      // Derive JWKS URI from the issuer URL
      // Issuer: https://auth.example.com/application/o/myapp/
      // JWKS:   https://auth.example.com/application/o/myapp/jwks/
      const jwksUri = new URL('jwks/', authentikConfig.issuer.endsWith('/') ? authentikConfig.issuer : authentikConfig.issuer + '/');
      this.jwks = jose.createRemoteJWKSet(jwksUri);
    }
    return this.jwks;
  }

  /**
   * Exchange authorization code for access and ID tokens
   */
  async exchangeCodeForTokens(code: string): Promise<TokenResponse> {
    try {
      const params = new URLSearchParams({
        grant_type: 'authorization_code',
        code,
        redirect_uri: authentikConfig.redirectUri,
        client_id: authentikConfig.clientId,
        client_secret: authentikConfig.clientSecret,
      });

      const response = await axios.post<TokenResponse>(
        authentikConfig.tokenEndpoint,
        params.toString(),
        {
          headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
          },
        }
      );

      logger.info('Successfully exchanged code for tokens');
      return response.data;
    } catch (error) {
      this.handleError(error, 'Failed to exchange code for tokens');
      throw error;
    }
  }

  /**
   * Fetch user information from Authentik using access token
   */
  async getUserInfo(accessToken: string): Promise<UserInfo> {
    try {
      const response = await axios.get<UserInfo>(
        authentikConfig.userInfoEndpoint,
        {
          headers: {
            Authorization: `Bearer ${accessToken}`,
          },
        }
      );

      logger.info('Successfully fetched user info', {
        sub: response.data.sub,
        email: response.data.email,
      });
      return response.data;
    } catch (error) {
      this.handleError(error, 'Failed to fetch user info');
      throw error;
    }
  }

  /**
   * Verify and decode ID token using JWKS
   */
  async verifyIdToken(idToken: string): Promise<jose.JWTPayload> {
    try {
      const { payload } = await jose.jwtVerify(idToken, this.getJwks(), {
        issuer: authentikConfig.issuer,
      });

      if (!payload.sub || !payload.email) {
        throw new Error('Invalid ID token payload: missing sub or email');
      }

      logger.info('ID token verified successfully via JWKS', {
        sub: payload.sub,
        email: payload.email,
      });

      return payload;
    } catch (error) {
      logger.error('Failed to verify ID token via JWKS', { error });
      throw new Error('Invalid ID token');
    }
  }

  /**
   * Refresh access token using refresh token
   */
  async refreshAccessToken(refreshToken: string): Promise<TokenResponse> {
    try {
      const params = new URLSearchParams({
        grant_type: 'refresh_token',
        refresh_token: refreshToken,
        client_id: authentikConfig.clientId,
        client_secret: authentikConfig.clientSecret,
      });

      const response = await axios.post<TokenResponse>(
        authentikConfig.tokenEndpoint,
        params.toString(),
        {
          headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
          },
        }
      );

      logger.info('Successfully refreshed access token');
      return response.data;
    } catch (error) {
      this.handleError(error, 'Failed to refresh access token');
      throw error;
    }
  }

  /**
   * Handle axios errors with detailed logging
   */
  private handleError(error: unknown, message: string): void {
    if (axios.isAxiosError(error)) {
      const axiosError = error as AxiosError;
      logger.error(message, {
        status: axiosError.response?.status,
        statusText: axiosError.response?.statusText,
        data: axiosError.response?.data,
        message: axiosError.message,
      });
    } else {
      logger.error(message, { error });
    }
  }
}

export default new AuthentikService();
