import 'dotenv/config';
import PocketBase from 'pocketbase';
import { acquireGraphUserToken } from './userAuth.ts';
import { createSession, getTableRows, getTableColumns } from './excel.ts';
import { mapRowToObject, upsertPocketBaseByKey } from './sync.ts';
import { execSync } from 'child_process';
import { writeFileSync } from 'fs';
import path from 'path';

function requireEnv(name: string): string {
  const val = process.env[name];
  if (!val) throw new Error(`Missing required env: ${name}`);
  return val;
}

function validateEnv() {
  const required = [
    'PB_COLLECTION',
    'PB_AGENT_EMAIL',
    'PB_AGENT_PASSWORD',
    'DRIVE_ID',
    'ITEM_ID',
    'EXCEL_TABLE',
    'CLIENT_ID',
    'CLIENT_SECRET',
    'TENANT_ID',
  ];
  for (const k of required) requireEnv(k);
}

// (Removed) In-memory hash cache: rely on upsert equality check to avoid unnecessary writes

async function runStartupAudit() {
  console.log('📊 Running startup audit to detect any data mismatches...');
  try {
    // Run audit from Excel_Audit/src/index.ts (go up to parent, then into Excel_Audit)
    const auditPath = path.resolve(__dirname, '../../Excel_Audit/src/index.ts');
    const auditOutput = execSync(`bun ${auditPath} --audit`, {
      cwd: __dirname,
      encoding: 'utf-8',
    });
    const auditResult = JSON.parse(auditOutput);
    const { summary } = auditResult;
    
    console.log(`✓ Audit complete: ${summary.excelCount} Excel, ${summary.pocketbaseCount} PocketBase, ${summary.mismatched} mismatches`);
    
    if (summary.mismatched > 0) {
      console.log(`⚠️ Found ${summary.mismatched} mismatches; applying corrections...`);
      
      // Build deltas.json from audit mismatches
      const deltas = {
        timestamp: new Date().toISOString(),
        summary,
        mismatches: auditResult.mismatches || [],
      };
      
      const deltasPath = path.resolve(__dirname, '..', 'applied_updates_deltas.json');
      writeFileSync(deltasPath, JSON.stringify(deltas, null, 2));
      console.log(`✓ Saved deltas to ${deltasPath}`);
      
      // Apply changes
      try {
        const applyOutput = execSync(`bun ${auditPath} --apply`, {
          cwd: __dirname,
          encoding: 'utf-8',
        });
        console.log(`✓ Applied updates:\n${applyOutput}`);
      } catch (err: any) {
        console.error(`✗ Apply failed: ${err?.message || err}`);
      }
    } else {
      console.log('✓ No mismatches detected; data is synchronized');
    }
  } catch (err: any) {
    console.warn(`⚠️ Startup audit failed (non-fatal): ${err?.message || err}`);
    console.log('Proceeding with normal polling anyway...');
  }
}

async function runOnce() {
  const pbUrl = (() => {
    const explicit = process.env.PB_DB;
    if (explicit) return explicit;
    const redirect = process.env.REDIRECT_URI;
    if (!redirect) throw new Error('Missing PB_DB or REDIRECT_URI');
    const u = new URL(redirect);
    return `${u.protocol}//${u.host}`;
  })();
  const collection = requireEnv('PB_COLLECTION');

  const pb = new PocketBase(pbUrl);
  const agentEmail = requireEnv('PB_AGENT_EMAIL');
  const agentPassword = requireEnv('PB_AGENT_PASSWORD');
  await pb.collection(process.env.PB_AUTH_COLLECTION || 'Users').authWithPassword(agentEmail, agentPassword);

  const tokenInfo = await acquireGraphUserToken();
  const userToken = tokenInfo.accessToken;
  if (tokenInfo.expiresAt) {
    const secondsLeft = Math.floor((tokenInfo.expiresAt - Date.now()) / 1000);
    if (secondsLeft <= 300) {
      console.warn(`⚠️ Graph token expires in ~${secondsLeft}s; provide a fresh delegated token soon.`);
    }
  }
  const sessionId = await createSession(userToken, true);
  const headers = await getTableColumns(userToken, sessionId);
  const rows = await getTableRows(userToken, sessionId);

  // Enforce explicit primary key header: Job_Number (case-insensitive match)
  const keyName = headers.find(h => h && typeof h === 'string' && h.toLowerCase() === 'job_number');
  if (!keyName) {
    throw new Error('Primary key header "Job_Number" is required and must match exactly');
  }
  const dataRows = rows.values;
  console.log(`ℹ️ Found ${dataRows.length} rows in Excel table`);

  let processed = 0;
  let skipped = 0;
  let errors = 0;
  for (const r of dataRows) {
    try {
      const obj = mapRowToObject(headers, r);
      const keyVal = obj[keyName];
      if (!keyVal) {
        console.warn('⚠️ Missing key; skipping row');
        continue;
      }
      const silent = processed % 100 !== 0;
      await upsertPocketBaseByKey(pb, collection, keyName, obj, silent);
      processed++;
      // Progress indicator
      if (processed % 100 === 0) {
        console.log(`ℹ️ Progress: ${processed}/${dataRows.length} records processed`);
      }
    } catch (err: any) {
      errors++;
      console.error(`✗ Error processing row: ${err?.message || err}`);
    }
  }
  if (processed || skipped || errors) {
    console.log(`ℹ️ Poller run: processed ${processed}, skipped ${skipped}, errors ${errors}`);
  }
}

async function main() {
  validateEnv();
  const intervalSec = Number(process.env.POLL_INTERVAL_SEC || 60);
  console.log(`✓ Starting poller (interval ${intervalSec}s)`);
  
  // Run startup audit to detect and fix any mismatches
  await runStartupAudit();
  
  let running = false;
  const runSafely = async () => {
    if (running) return;
    running = true;
    try {
      await runOnce();
    } catch (err: any) {
      console.error('✗ Poll run failed:', err?.message || err);
    } finally {
      running = false;
    }
  };

  await runSafely();
  setInterval(runSafely, intervalSec * 1000);
}

main().catch(err => {
  console.error('✗ Poller failed to start:', err?.message || err);
  process.exit(1);
});
