/home/bdqbpbxa/dev-subdomains/admin.pixory.goodface.com.ua/src/policies/auth0.ts
import utils from"@strapi/utils";
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
import axios from 'axios';
import { Context } from 'koa';
import { env } from 'src/config/env';
import { getIpRegion } from "src/api/utils/localization/services/localization";

const { PolicyError } = utils.errors;

interface UserInfo {
  sub: string;
  email: string;
  email_verified: boolean;
}

const client = jwksClient({
  jwksUri: `${env.auth0.issuerBaseUrl}.well-known/jwks.json`,
});

function verifyTokenAsync(token: string, audience: string, issuer: string): Promise<any> {
  return new Promise((resolve, reject) => {
    jwt.verify(
      token,
      (header, callback) => {
        if (!header.kid) {
          return callback(new Error('Missing kid in token header'));
        }
        client.getSigningKey(header.kid, function (err, key) {
          if (err) return callback(err);
          const signingKey = key.getPublicKey();
          callback(null, signingKey);
        });
      },
      {
        audience,
        issuer,
        algorithms: ['RS256'],
      },
      (err, decoded) => {
        if (err) return reject(err);
        resolve(decoded);
      }
    );
  });
}

async function getUserInfo(accessToken: string): Promise<UserInfo> {
  try {
    const response = await axios.get(`${env.auth0.issuerBaseUrl}userinfo`, {
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    });
    return response.data;
  } catch (error) {
    throw error;
  }
}


async function createUserFromAuth0(ctx: Context, userInfo: UserInfo, strapi: any) {
  const authenticatedRole = await strapi.query('plugin::users-permissions.role').findOne({
    where: { type: 'authenticated' }
  });

  const userIp = ctx.request.ip || (ctx.request.headers['x-forwarded-for'] as string)?.split(',')[0];
  const ipData = await getIpRegion(userIp);

  return await strapi.query('plugin::users-permissions.user').create({
    data: {
      sub: userInfo.sub,
      email: userInfo.email,
      confirmed: userInfo.email_verified,
      username: userInfo.email.split('@')[0],
      provider: userInfo.sub.split('|')[0],
      currency: ipData.currency,
      timezone: ipData.timezone,
      role: authenticatedRole.id,
    }, 
  });
}

const auth0Policy = async (ctx: Context, config, { strapi }) => {
  try {
    const authHeader = ctx.request.headers.authorization;

    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      throw new PolicyError('Authorization header is missing or invalid', { status: 401 });
    }

    const token = authHeader.split(' ')[1];

    const decoded = await verifyTokenAsync(
      token,
      env.auth0.audience,
      env.auth0.issuerBaseUrl
    );
    
    let user = await strapi.db.query('plugin::users-permissions.user').findOne({
      where: { sub: decoded.sub }
    });

    if (!user) {
      const authHeader = ctx.request.headers.authorization;
      const accessToken = authHeader.split(' ')[1];
      
      const userInfo = await getUserInfo(accessToken);

      user = await createUserFromAuth0(ctx, userInfo, strapi);
    }
    ctx.state.user = {
      id: user.id,
      sub: decoded.sub,
      ...decoded,
    };

    return true;
  } catch (error) {
    if (error instanceof PolicyError) {
      throw error;
    }

    console.log(error)
    
    if (error.name === 'JsonWebTokenError') {
      throw new PolicyError('Invalid token format', { status: 401 });
    }
    
    if (error.name === 'TokenExpiredError') {
      throw new PolicyError('Token has expired', { status: 401 });
    }
    
    if (error.name === 'NotBeforeError') {
      throw new PolicyError('Token not active yet', { status: 401 });
    }

    // Handle JWKS errors
    if (error.message && error.message.includes('kid')) {
      throw new PolicyError('Invalid token key identifier', { status: 401 });
    }

    // Generic error fallback
    strapi.log.error('Auth0 policy error:', error);
    throw new PolicyError('Authentication failed', { status: 401 });
  }
};

export default auth0Policy;