// server/utils/userSync.ts
// Shared utility for syncing user data from JWT tokens to database

import Database from 'better-sqlite3';
import { join } from 'path';
import { existsSync, mkdirSync } from 'fs';

// Helper to get database path
const getDbPath = () => {
  const dbDir = join(process.cwd(), 'data');
  if (!existsSync(dbDir)) {
    mkdirSync(dbDir, { recursive: true });
  }
  return join(dbDir, 'users.db');
};

// Initialize database and create table if not exists
const initDb = () => {
  const dbPath = getDbPath();
  const db = new Database(dbPath);
  
  // Create table if not exists
  db.exec(`
    CREATE TABLE IF NOT EXISTS users (
      id TEXT PRIMARY KEY,
      namaLengkap TEXT NOT NULL,
      namaUser TEXT UNIQUE NOT NULL,
      email TEXT,
      tipeUser TEXT DEFAULT '',
      lastLogin INTEGER,
      roles TEXT DEFAULT '[]',
      realmRoles TEXT DEFAULT '[]',
      accountRoles TEXT DEFAULT '[]',
      resourceRoles TEXT DEFAULT '[]',
      groups TEXT DEFAULT '[]',
      given_name TEXT,
      family_name TEXT,
      createdAt INTEGER DEFAULT (strftime('%s', 'now')),
      updatedAt INTEGER DEFAULT (strftime('%s', 'now'))
    )
  `);
  
  // Migration: Check and add missing columns one by one
  try {
    const tableInfo = db.prepare("PRAGMA table_info(users)").all() as any[];
    const columnNames = tableInfo.map(col => col.name);
    
    // Add missing columns one by one
    if (!columnNames.includes('realmRoles')) {
      db.exec(`ALTER TABLE users ADD COLUMN realmRoles TEXT DEFAULT '[]'`);
      console.log('✅ Added column: realmRoles');
    }
    
    if (!columnNames.includes('accountRoles')) {
      db.exec(`ALTER TABLE users ADD COLUMN accountRoles TEXT DEFAULT '[]'`);
      console.log('✅ Added column: accountRoles');
    }
    
    if (!columnNames.includes('resourceRoles')) {
      db.exec(`ALTER TABLE users ADD COLUMN resourceRoles TEXT DEFAULT '[]'`);
      console.log('✅ Added column: resourceRoles');
    }
    
    if (!columnNames.includes('lastLogin')) {
      db.exec(`ALTER TABLE users ADD COLUMN lastLogin INTEGER`);
      console.log('✅ Added column: lastLogin');
    }
    
    if (!columnNames.includes('given_name')) {
      db.exec(`ALTER TABLE users ADD COLUMN given_name TEXT`);
      console.log('✅ Added column: given_name');
    }
    
    if (!columnNames.includes('family_name')) {
      db.exec(`ALTER TABLE users ADD COLUMN family_name TEXT`);
      console.log('✅ Added column: family_name');
    }
  } catch (e: any) {
    console.error('❌ Migration error:', e.message);
    // Don't throw, continue with existing schema
  }
  
  return db;
};

// Helper function to decode JWT token payload
const decodeTokenPayload = (token: string | undefined): any | null => {
  if (!token) return null;
  try {
    const parts = token.split(".");
    if (parts.length < 2) return null;
    const payloadBase64 = parts[1];
    return JSON.parse(Buffer.from(payloadBase64, "base64").toString());
  } catch (e) {
    console.error("❌ Failed to decode token payload:", e);
    return null;
  }
};

/**
 * Sync user data from JWT tokens to database
 * @param idToken - The ID token from Keycloak
 * @param accessToken - The access token from Keycloak
 * @param loginTime - Optional login timestamp (from session createdAt). If not provided, uses current time
 * @returns Object with success status and action taken
 */
export const syncUserFromTokens = (
  idToken: string,
  accessToken: string,
  loginTime?: number
): { success: boolean; action: 'created' | 'updated' | 'unchanged'; message: string } => {
  try {
    // Decode tokens
    const idTokenPayload = decodeTokenPayload(idToken);
    const accessTokenPayload = decodeTokenPayload(accessToken);

    if (!idTokenPayload) {
      throw new Error('Invalid ID token');
    }

    // Extract user data
    const userId = idTokenPayload.sub;
    const namaLengkap = idTokenPayload.name || 
                       `${idTokenPayload.given_name || ''} ${idTokenPayload.family_name || ''}`.trim() ||
                       idTokenPayload.preferred_username;
    const namaUser = idTokenPayload.preferred_username || 
                    idTokenPayload.email?.split('@')[0];
    const email = idTokenPayload.email;

    if (!userId || !namaUser) {
      throw new Error('Missing required user data (id or username)');
    }

    // Extract roles from different sources
    const realmRoles = accessTokenPayload?.realm_access?.roles || 
                       idTokenPayload?.realm_access?.roles || 
                       [];
    
    const accountRoles = accessTokenPayload?.resource_access?.account?.roles || 
                         idTokenPayload?.resource_access?.account?.roles || 
                         [];
    
    // Extract resource roles (from all resources in resource_access)
    const resourceRoles: string[] = [];
    if (accessTokenPayload?.resource_access) {
      Object.keys(accessTokenPayload.resource_access).forEach(resourceName => {
        if (resourceName !== 'account') { // Exclude account, already handled
          const resourceRolesArray = accessTokenPayload.resource_access[resourceName]?.roles || [];
          resourceRoles.push(...resourceRolesArray.map((role: string) => `${resourceName}:${role}`));
        }
      });
    }
    if (idTokenPayload?.resource_access) {
      Object.keys(idTokenPayload.resource_access).forEach(resourceName => {
        if (resourceName !== 'account') { // Exclude account, already handled
          const resourceRolesArray = idTokenPayload.resource_access[resourceName]?.roles || [];
          resourceRoles.push(...resourceRolesArray.map((role: string) => `${resourceName}:${role}`));
        }
      });
    }
    
    // Legacy: Combined roles (for backward compatibility)
    const roles = [...realmRoles, ...accountRoles, ...resourceRoles];
    
    // Keycloak uses 'groups_join' in access token, not 'groups'
    const groups = accessTokenPayload?.groups_join || 
                   accessTokenPayload?.groups || 
                   idTokenPayload?.groups_join ||
                   idTokenPayload?.groups || 
                   [];

    // Determine tipeUser from groups or roles if possible
    // Extract from groups path (e.g., "/Instalasi STIM/Devops/Superadmin" -> "Superadmin")
    let tipeUser = '';
    if (Array.isArray(groups) && groups.length > 0) {
      // Get the last group path and extract the last segment
      const lastGroup = groups[groups.length - 1];
      if (typeof lastGroup === 'string') {
        const parts = lastGroup.split('/').filter(Boolean);
        if (parts.length > 0) {
          tipeUser = parts[parts.length - 1]; // Get last part of path
        }
      }
    }
    // If no tipeUser from groups, you can also check roles or leave empty for manual entry

    // Use provided loginTime or current time (convert to seconds if in milliseconds)
    const lastLoginTimestamp = loginTime 
      ? (loginTime > 10000000000 ? Math.floor(loginTime / 1000) : loginTime) // Convert ms to seconds if needed
      : Math.floor(Date.now() / 1000);

    // Initialize database
    const db = initDb();
    
    // Check if user exists
    const existingUser = db.prepare('SELECT * FROM users WHERE id = ?').get(userId) as any;
    
    const rolesJson = JSON.stringify(Array.isArray(roles) ? roles : []);
    const realmRolesJson = JSON.stringify(Array.isArray(realmRoles) ? realmRoles : []);
    const accountRolesJson = JSON.stringify(Array.isArray(accountRoles) ? accountRoles : []);
    const resourceRolesJson = JSON.stringify(Array.isArray(resourceRoles) ? resourceRoles : []);
    const groupsJson = JSON.stringify(Array.isArray(groups) ? groups : []);

    if (existingUser) {
      // User exists - check if data needs updating
      // Note: tipeUser is only updated if it's empty in database (to preserve manual edits)
      const needsUpdate = 
        existingUser.namaLengkap !== namaLengkap ||
        existingUser.namaUser !== namaUser ||
        existingUser.email !== email ||
        existingUser.roles !== rolesJson ||
        existingUser.realmRoles !== realmRolesJson ||
        existingUser.accountRoles !== accountRolesJson ||
        existingUser.resourceRoles !== resourceRolesJson ||
        existingUser.groups !== groupsJson ||
        existingUser.given_name !== (idTokenPayload.given_name || null) ||
        existingUser.family_name !== (idTokenPayload.family_name || null) ||
        (existingUser.tipeUser === '' && tipeUser !== ''); // Only update if empty

      // Always update lastLogin when loginTime is provided (user is logging in)
      // Check if lastLogin needs updating (if loginTime provided or new timestamp is newer)
      const existingLastLogin = existingUser.lastLogin || 0;
      const needsLastLoginUpdate = loginTime !== undefined 
        ? true // Always update on login when loginTime is provided
        : (lastLoginTimestamp > existingLastLogin); // Only update if newer when not a login event

      // Update if any data changed OR if lastLogin needs updating
      if (needsUpdate || needsLastLoginUpdate) {
        // Update user data
        // Only update tipeUser if it's currently empty (preserve manual edits)
        const updateTipeUser = existingUser.tipeUser === '' ? tipeUser : existingUser.tipeUser;
        
        db.prepare(`
          UPDATE users 
          SET namaLengkap = ?,
              namaUser = ?,
              email = ?,
              roles = ?,
              realmRoles = ?,
              accountRoles = ?,
              resourceRoles = ?,
              groups = ?,
              given_name = ?,
              family_name = ?,
              tipeUser = ?,
              lastLogin = ?,
              updatedAt = strftime('%s', 'now')
          WHERE id = ?
        `).run(
          namaLengkap,
          namaUser,
          email || null,
          rolesJson,
          realmRolesJson,
          accountRolesJson,
          resourceRolesJson,
          groupsJson,
          idTokenPayload.given_name || null,
          idTokenPayload.family_name || null,
          updateTipeUser,
          lastLoginTimestamp, // Update lastLogin timestamp from session
          userId
        );
        
        if (needsLastLoginUpdate && !needsUpdate) {
          console.log("✅ User lastLogin updated:", userId, "new timestamp:", lastLoginTimestamp);
        } else {
          console.log("✅ User data updated:", userId);
        }
        db.close();
        return { 
          success: true, 
          action: 'updated',
          message: 'User data updated successfully' 
        };
      } else {
        console.log("ℹ️ User data unchanged:", userId);
        db.close();
        return { 
          success: true, 
          action: 'unchanged',
          message: 'User data is up to date' 
        };
      }
    } else {
      // New user - insert
      console.log("➕ Inserting new user:", {
        userId,
        namaLengkap,
        namaUser,
        email,
        lastLoginTimestamp
      });
      
      db.prepare(`
        INSERT INTO users (
          id, namaLengkap, namaUser, email, roles, realmRoles, accountRoles, resourceRoles, groups, 
          given_name, family_name, tipeUser, lastLogin
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
      `).run(
        userId,
        namaLengkap,
        namaUser,
        email || null,
        rolesJson,
        realmRolesJson,
        accountRolesJson,
        resourceRolesJson,
        groupsJson,
        idTokenPayload.given_name || null,
        idTokenPayload.family_name || null,
        tipeUser, // tipeUser - extracted from groups or empty
        lastLoginTimestamp  // lastLogin - from session createdAt
      );
      
      console.log("✅ New user saved to database:", {
        userId,
        namaUser,
        namaLengkap,
        lastLogin: lastLoginTimestamp ? new Date(lastLoginTimestamp * 1000).toISOString() : 'null'
      });
      db.close();
      return { 
        success: true, 
        action: 'created',
        message: `New user ${namaUser} saved successfully` 
      };
    }
  } catch (error: any) {
    console.error("❌ Error syncing user:", error);
    throw error;
  }
};

