#!/usr/bin/env node
/**
 * Auth0 Setup Script for Traccar GPS Integration
 *
 * This script sets up Auth0 roles and actions needed for Traccar GPS tracking
 * to work with OpenID Connect authentication.
 *
 * Usage:
 *   node setup-auth0-traccar.js --token=<AUTH0_TOKEN> --domain=<AUTH0_DOMAIN> --traccar-url=<URL> --admins=<emails>
 *
 * Or with environment variables:
 *   AUTH0_MANAGEMENT_TOKEN=<token> \
 *   AUTH0_DOMAIN=<domain> \
 *   TRACCAR_URL=<url> \
 *   ADMIN_EMAILS=<comma-separated-emails> \
 *   node setup-auth0-traccar.js
 *
 * Examples:
 *   node setup-auth0-traccar.js \
 *     --token=eyJ... \
 *     --domain=my-tenant.us.auth0.com \
 *     --traccar-url=https://traccar.myapp.com \
 *     --admins=admin@example.com,backup-admin@example.com
 *
 * What this script does:
 * 1. Creates ADMINISTRATOR and COORDINATOR roles in Auth0
 * 2. Creates a Post Login Action that adds roles to tokens as "groups"
 * 3. Deploys the action to the Login flow
 * 4. Assigns ADMINISTRATOR role to specified users (if they exist in Auth0)
 *
 * After running this script:
 * - Users with ADMINISTRATOR role get admin access to Traccar
 * - Users with COORDINATOR role get standard access to Traccar
 * - Users without either role cannot access Traccar
 * - Manage roles in Auth0 Dashboard → User Management → Users → [user] → Roles
 */

// Parse command line arguments
function parseArgs() {
  const args = {};
  process.argv.slice(2).forEach(arg => {
    if (arg.startsWith('--')) {
      const [key, value] = arg.slice(2).split('=');
      args[key] = value;
    }
  });
  return args;
}

const args = parseArgs();

// Configuration - from args, env vars, or defaults
const AUTH0_DOMAIN = args.domain || process.env.AUTH0_DOMAIN || '';
const TRACCAR_URL = args['traccar-url'] || process.env.TRACCAR_URL || '';
const TRACCAR_NAMESPACE = TRACCAR_URL; // Namespace matches the Traccar URL

// Users to assign ADMINISTRATOR role to (comma-separated)
const ADMIN_EMAILS = (args.admins || process.env.ADMIN_EMAILS || '')
  .split(',')
  .map(e => e.trim())
  .filter(e => e.length > 0);

async function main() {
  const token = args.token || process.env.AUTH0_MANAGEMENT_TOKEN;

  // Validate required parameters
  const missing = [];
  if (!token) missing.push('--token or AUTH0_MANAGEMENT_TOKEN');
  if (!AUTH0_DOMAIN) missing.push('--domain or AUTH0_DOMAIN');
  if (!TRACCAR_URL) missing.push('--traccar-url or TRACCAR_URL');
  if (ADMIN_EMAILS.length === 0) missing.push('--admins or ADMIN_EMAILS');

  if (missing.length > 0) {
    console.error('Missing required parameters:');
    missing.forEach(m => console.error(`  - ${m}`));
    console.error('\nUsage:');
    console.error('  node setup-auth0-traccar.js \\');
    console.error('    --token=<AUTH0_MANAGEMENT_TOKEN> \\');
    console.error('    --domain=<your-tenant.us.auth0.com> \\');
    console.error('    --traccar-url=<https://traccar.yourdomain.com> \\');
    console.error('    --admins=<admin@example.com,other@example.com>');
    console.error('\nExample:');
    console.error('  node setup-auth0-traccar.js \\');
    console.error('    --token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9... \\');
    console.error('    --domain=my-company.us.auth0.com \\');
    console.error('    --traccar-url=https://traccar.myapp.com \\');
    console.error('    --admins=john@company.com,jane@company.com');
    process.exit(1);
  }

  const headers = {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  };

  console.log('=== Auth0 Traccar Setup ===\n');
  console.log(`Auth0 Domain: ${AUTH0_DOMAIN}`);
  console.log(`Traccar URL:  ${TRACCAR_URL}`);
  console.log(`Admin Emails: ${ADMIN_EMAILS.join(', ')}\n`);

  // Step 1: Create roles
  console.log('1. Creating roles...');

  const adminRole = await createRole(headers, {
    name: 'ADMINISTRATOR',
    description: 'Full admin access to VIP Coordinator and Traccar GPS tracking'
  });
  console.log(`   - ADMINISTRATOR role: ${adminRole ? 'created' : 'already exists'}`);

  const coordRole = await createRole(headers, {
    name: 'COORDINATOR',
    description: 'Coordinator access to VIP Coordinator and Traccar GPS tracking'
  });
  console.log(`   - COORDINATOR role: ${coordRole ? 'created' : 'already exists'}`);

  // Get role IDs
  const roles = await getRoles(headers);
  const adminRoleId = roles.find(r => r.name === 'ADMINISTRATOR')?.id;
  const coordRoleId = roles.find(r => r.name === 'COORDINATOR')?.id;

  console.log(`   Admin Role ID: ${adminRoleId}`);
  console.log(`   Coordinator Role ID: ${coordRoleId}`);

  // Step 2: Create the Post Login Action
  console.log('\n2. Creating Post Login Action...');

  const actionCode = `
exports.onExecutePostLogin = async (event, api) => {
  const namespace = '${TRACCAR_NAMESPACE}';

  if (event.authorization && event.authorization.roles) {
    // Add roles as "groups" claim (what Traccar expects)
    api.idToken.setCustomClaim(namespace + '/groups', event.authorization.roles);
    api.accessToken.setCustomClaim(namespace + '/groups', event.authorization.roles);
  }
};
`;

  const action = await createOrUpdateAction(headers, {
    name: 'Add Roles to Traccar Groups',
    code: actionCode.trim(),
    supported_triggers: [{ id: 'post-login', version: 'v3' }],
    runtime: 'node18'
  });

  if (action) {
    console.log(`   - Action created: ${action.id}`);

    // Deploy the action
    console.log('\n3. Deploying action...');
    await deployAction(headers, action.id);
    console.log('   - Action deployed');

    // Add to login flow
    console.log('\n4. Adding action to Login flow...');
    await addActionToFlow(headers, action.id);
    console.log('   - Action added to flow');
  } else {
    console.log('   - Action already exists or failed to create');
  }

  // Step 3: Assign ADMINISTRATOR role to admin users
  console.log('\n5. Assigning ADMINISTRATOR role to admin users...');

  for (const email of ADMIN_EMAILS) {
    const user = await findUserByEmail(headers, email);
    if (user) {
      await assignRoleToUser(headers, user.user_id, adminRoleId);
      console.log(`   - ${email}: ADMINISTRATOR role assigned`);
    } else {
      console.log(`   - ${email}: User not found (will get role on first login)`);
    }
  }

  console.log('\n=== Setup Complete ===');
  console.log('\nNext steps:');
  console.log('1. Update Traccar config with these settings:');
  console.log(`   <entry key="openid.group">${TRACCAR_NAMESPACE}/groups</entry>`);
  console.log('   <entry key="openid.adminGroup">ADMINISTRATOR</entry>');
  console.log('   <entry key="openid.allowGroup">ADMINISTRATOR,COORDINATOR</entry>');
  console.log('\n2. Restart Traccar container');
  console.log('\n3. Test login with an admin user');
}

async function createRole(headers, roleData) {
  try {
    const response = await fetch(`https://${AUTH0_DOMAIN}/api/v2/roles`, {
      method: 'POST',
      headers,
      body: JSON.stringify(roleData)
    });

    if (response.status === 409) {
      // Role already exists
      return null;
    }

    if (!response.ok) {
      const error = await response.text();
      console.error(`Failed to create role ${roleData.name}:`, error);
      return null;
    }

    return await response.json();
  } catch (error) {
    console.error(`Error creating role ${roleData.name}:`, error.message);
    return null;
  }
}

async function getRoles(headers) {
  try {
    const response = await fetch(`https://${AUTH0_DOMAIN}/api/v2/roles`, {
      method: 'GET',
      headers
    });

    if (!response.ok) {
      console.error('Failed to get roles');
      return [];
    }

    return await response.json();
  } catch (error) {
    console.error('Error getting roles:', error.message);
    return [];
  }
}

async function createOrUpdateAction(headers, actionData) {
  try {
    // Check if action exists
    const listResponse = await fetch(`https://${AUTH0_DOMAIN}/api/v2/actions/actions?actionName=${encodeURIComponent(actionData.name)}`, {
      method: 'GET',
      headers
    });

    if (listResponse.ok) {
      const actions = await listResponse.json();
      const existing = actions.actions?.find(a => a.name === actionData.name);
      if (existing) {
        // Update existing action
        const updateResponse = await fetch(`https://${AUTH0_DOMAIN}/api/v2/actions/actions/${existing.id}`, {
          method: 'PATCH',
          headers,
          body: JSON.stringify({ code: actionData.code })
        });

        if (updateResponse.ok) {
          return await updateResponse.json();
        }
      }
    }

    // Create new action
    const response = await fetch(`https://${AUTH0_DOMAIN}/api/v2/actions/actions`, {
      method: 'POST',
      headers,
      body: JSON.stringify(actionData)
    });

    if (!response.ok) {
      const error = await response.text();
      console.error('Failed to create action:', error);
      return null;
    }

    return await response.json();
  } catch (error) {
    console.error('Error creating action:', error.message);
    return null;
  }
}

async function deployAction(headers, actionId) {
  try {
    const response = await fetch(`https://${AUTH0_DOMAIN}/api/v2/actions/actions/${actionId}/deploy`, {
      method: 'POST',
      headers
    });

    if (!response.ok) {
      const error = await response.text();
      console.error('Failed to deploy action:', error);
      return false;
    }

    return true;
  } catch (error) {
    console.error('Error deploying action:', error.message);
    return false;
  }
}

async function addActionToFlow(headers, actionId) {
  try {
    // Get current flow bindings
    const getResponse = await fetch(`https://${AUTH0_DOMAIN}/api/v2/actions/triggers/post-login/bindings`, {
      method: 'GET',
      headers
    });

    if (!getResponse.ok) {
      console.error('Failed to get flow bindings');
      return false;
    }

    const currentBindings = await getResponse.json();

    // Check if action is already in flow
    const alreadyBound = currentBindings.bindings?.some(b => b.action?.id === actionId);
    if (alreadyBound) {
      console.log('   - Action already in flow');
      return true;
    }

    // Add action to flow
    const newBindings = [
      ...(currentBindings.bindings || []).map(b => ({ ref: { type: 'action_id', value: b.action.id } })),
      { ref: { type: 'action_id', value: actionId } }
    ];

    const updateResponse = await fetch(`https://${AUTH0_DOMAIN}/api/v2/actions/triggers/post-login/bindings`, {
      method: 'PATCH',
      headers,
      body: JSON.stringify({ bindings: newBindings })
    });

    if (!updateResponse.ok) {
      const error = await updateResponse.text();
      console.error('Failed to update flow bindings:', error);
      return false;
    }

    return true;
  } catch (error) {
    console.error('Error adding action to flow:', error.message);
    return false;
  }
}

async function findUserByEmail(headers, email) {
  try {
    const response = await fetch(`https://${AUTH0_DOMAIN}/api/v2/users-by-email?email=${encodeURIComponent(email)}`, {
      method: 'GET',
      headers
    });

    if (!response.ok) {
      return null;
    }

    const users = await response.json();
    return users[0] || null;
  } catch (error) {
    console.error(`Error finding user ${email}:`, error.message);
    return null;
  }
}

async function assignRoleToUser(headers, userId, roleId) {
  try {
    const response = await fetch(`https://${AUTH0_DOMAIN}/api/v2/users/${encodeURIComponent(userId)}/roles`, {
      method: 'POST',
      headers,
      body: JSON.stringify({ roles: [roleId] })
    });

    if (!response.ok && response.status !== 204) {
      const error = await response.text();
      console.error(`Failed to assign role to user:`, error);
      return false;
    }

    return true;
  } catch (error) {
    console.error('Error assigning role:', error.message);
    return false;
  }
}

main().catch(console.error);
