<?php
// includes/imap_lib.php (fixed, compat mode ready)
declare(strict_types=1);

require_once __DIR__ . '/db.php';
require_once __DIR__ . '/crypto.php';

if (!defined('IMAP_MODE')) define('IMAP_MODE', 'compat');
if (!defined('DEFAULT_FETCH_MINUTES')) define('DEFAULT_FETCH_MINUTES', 15);

// -------------------- Helpers --------------------

/** Build IMAP mailbox string from imap_accounts row */
function imap_mailbox_string(array $imap): string {
  $host = $imap['host'] ?? 'imap.gmail.com';
  $port = (int)($imap['port'] ?? 993);
  $flags = $imap['flags'] ?? '';
  if (!$flags) {
    $enc = strtolower($imap['encryption'] ?? 'ssl');
    if ($enc === 'ssl') $flags = '/imap/ssl/validate-cert';
    elseif ($enc === 'tls') $flags = '/imap/tls/validate-cert';
    else $flags = '/imap/novalidate-cert';
  }
  return sprintf('{%s:%d%s}', $host, $port, $flags);
}

/** Read message body as text (decode quoted-printable; strip HTML) */
function imap_get_text($stream, int $uid): string {
  $raw = @imap_body($stream, (string)$uid, FT_UID);
  if (!$raw) return '';
  $raw = quoted_printable_decode($raw);
  $text = strip_tags($raw);
  if (!$text) $text = $raw;
  return (string)$text;
}

/** Parse RFC822 list and check if contains a given address */
function addrlist_contains($headerVal, string $needleEmail): bool {
  if (!$headerVal || !$needleEmail) return false;
  $list = @imap_rfc822_parse_adrlist($headerVal, '');
  if (!$list) return false;
  $needle = strtolower(trim($needleEmail));
  foreach ($list as $a) {
    $addr = strtolower(trim(($a->mailbox ?? '').'@'.($a->host ?? '')));
    if ($addr === $needle) return true;
  }
  return false;
}

/** Extract Delivered-To from raw headers */
function header_delivered_to($stream, int $uid): string {
  $hdr = @imap_fetchheader($stream, (string)$uid, FT_UID);
  if (!$hdr) return '';
  if (preg_match('/^Delivered-To:\s*(.+)$/im', $hdr, $m)) {
    return trim(strtolower($m[1]));
  }
  return '';
}

/** Sender domain from From: */
function imap_from_domain($fromHeader): string {
  if (!$fromHeader) return '';
  $list = @imap_rfc822_parse_adrlist($fromHeader, '');
  if (!$list || empty($list[0])) return '';
  return strtolower(trim($list[0]->host ?? ''));
}

/** Extract multiple codes using regexes (kept for completeness) */
function extract_codes(string $text, array $regexes): array {
  $found = [];
  foreach ($regexes as $rx) {
    if (@preg_match_all($rx, $text, $m)) {
      $candidates = $m[1] ?? [];
      foreach ($candidates as $code) {
        $code = preg_replace('/\s+/', '', trim((string)$code));
        if ($code !== '') $found[] = $code;
      }
    }
  }
  // include spaced codes
  if (@preg_match_all('/\b(\d(?:\s?\d){5})\b/u', $text, $m2)) {
    foreach (($m2[1] ?? []) as $c) $found[] = preg_replace('/\s+/', '', $c);
  }
  // unique & 6-digit only
  $found = array_values(array_unique(array_filter($found, fn($x)=>preg_match('/^\d{6}$/', $x))));
  return $found;
}

/** Choose the best 6-digit code from BODY near keywords/phrases */
function pick_best_code(string $body, array $phrases, array $regexes): string {
  $keywords = ['passcode','code','codigo','código','acceso','acesso','accesso','one-time','otp'];
  $text = (string)$body;

  // anchors from phrases & keywords
  $anchors = [];
  foreach ($phrases as $ph) {
    if ($ph && ($pos = stripos($text, (string)$ph)) !== false) $anchors[] = $pos;
  }
  foreach ($keywords as $kw) {
    if (($pos = stripos($text, $kw)) !== false) $anchors[] = $pos;
  }

  // candidates from body
  $cands = [];
  $all_rx = $regexes;
  $all_rx[] = '/\b(\d{6})\b/u';
  $all_rx[] = '/\b(\d(?:\s?\d){5})\b/u';
  foreach ($all_rx as $rx) {
    if (@preg_match_all($rx, $text, $m, PREG_OFFSET_CAPTURE)) {
      $grp = $m[1] ?? [];
      foreach ($grp as $t) {
        $raw = (string)$t[0];
        $pos = (int)$t[1];
        $norm = preg_replace('/\s+/', '', $raw);
        if ($norm !== '' && preg_match('/^\d{6}$/', $norm)) {
          $cands[] = ['code'=>$norm, 'pos'=>$pos];
        }
      }
    }
  }
  if (!$cands) return '';

  // Prefer closest after an anchor within 400 chars
  if ($anchors) {
    $best = null; $bestDist = PHP_INT_MAX;
    foreach ($anchors as $a) {
      foreach ($cands as $c) {
        if ($c['pos'] >= $a && $c['pos'] <= $a + 400) {
          $dist = $c['pos'] - $a;
          if ($dist < $bestDist) { $bestDist = $dist; $best = $c; }
        }
      }
    }
    if ($best) return (string)$best['code'];
  }

  // Try lines with keywords
  $lines = preg_split('/\r?\n/', $text);
  foreach ($lines as $line) {
    $lower = mb_strtolower($line, 'UTF-8');
    $hasKw = false;
    foreach ($keywords as $kw) { if (strpos($lower, $kw)!==false) { $hasKw = true; break; } }
    if ($hasKw) {
      if (@preg_match('/\b(\d{6})\b/u', $line, $mm)) return (string)$mm[1];
      if (@preg_match('/\b(\d(?:\s?\d){5})\b/u', $line, $mm2)) return preg_replace('/\s+/', '', (string)$mm2[1]);
    }
  }

  // Fallback: first candidate
  return (string)$cands[0]['code'];
}

/** Platform name by id (cached) */
function platform_name_by_id(int $id): string {
  static $cache = [];
  if (isset($cache[$id])) return $cache[$id];
  try {
    $st = pdo()->prepare('SELECT name FROM platforms WHERE id = ?');
    $st->execute([$id]);
    $name = (string)($st->fetchColumn() ?: '');
  } catch (Throwable $e) { $name = ''; }
  return $cache[$id] = $name;
}

/** Get phrases for platform (imap_keys or key_phrases) */
function get_phrases(int $platform_id): array {
  $pdo = pdo();
  try {
    $st = $pdo->prepare('SELECT phrase_key FROM imap_keys WHERE platform_id = ? AND enabled = 1');
    $st->execute([$platform_id]);
    $rows = $st->fetchAll(PDO::FETCH_COLUMN);
    if ($rows) return array_values(array_filter($rows));
  } catch (Throwable $e) {}
  try {
    $st = $pdo->prepare('SELECT phrase FROM key_phrases WHERE platform_id = ? AND active = 1');
    $st->execute([$platform_id]);
    $rows = $st->fetchAll(PDO::FETCH_COLUMN);
    return array_values(array_filter($rows));
  } catch (Throwable $e) {}
  return [];
}

/** Get regex list for platform (regex_patterns or imap_keys.regex) */
function get_regexes(int $platform_id): array {
  $pdo = pdo();
  $rx = [];
  try {
    $st = $pdo->prepare('SELECT pattern FROM regex_patterns WHERE platform_id = ? AND active = 1');
    $st->execute([$platform_id]);
    $rx = array_merge($rx, array_values(array_filter($st->fetchAll(PDO::FETCH_COLUMN))));
  } catch (Throwable $e) {}
  try {
    $st = $pdo->prepare('SELECT regex FROM imap_keys WHERE platform_id = ? AND enabled = 1 AND regex IS NOT NULL AND regex <> ""');
    $st->execute([$platform_id]);
    $rx = array_merge($rx, array_values(array_filter($st->fetchAll(PDO::FETCH_COLUMN))));
  } catch (Throwable $e) {}
  if (!$rx) $rx = ['/\b(\d{6})\b/u'];
  return $rx;
}

/** Try to locate the correct IMAP account. Fallback to any enabled account. */
function get_imap_for_account(int $client_id, int $platform_id, string $account_email): ?array {
  $pdo = pdo();
  // Try join with assigned_accounts if it has imap_account_id
  try {
    $st = $pdo->prepare('SELECT ia.* FROM assigned_accounts aa JOIN imap_accounts ia ON ia.id = aa.imap_account_id WHERE aa.email = ? AND aa.platform_id = ? LIMIT 1');
    $st->execute([$account_email, $platform_id]);
    $row = $st->fetch(PDO::FETCH_ASSOC);
    if ($row) return $row;
  } catch (Throwable $e) {}
  // Fallback: first enabled
  try {
    $st = $pdo->query('SELECT * FROM imap_accounts WHERE enabled = 1 ORDER BY id DESC LIMIT 1');
    $row = $st->fetch(PDO::FETCH_ASSOC);
    if ($row) return $row;
  } catch (Throwable $e) {}
  return null;
}

/** Upsert to code_messages; fallback to codes_log */
function upsert_code_message_simple(int $imap_id, int $platform_id, string $account_email, int $uid, string $subject, string $code, string $dateStr): void {
  $pdo = pdo();
  $dt = date('Y-m-d H:i:s', strtotime($dateStr ?: 'now'));
  try {
    $sql = "INSERT INTO code_messages (imap_account_id, uid, platform_id, account_email, subject, internal_date, code)
            VALUES (?,?,?,?,?,?,?)
            ON DUPLICATE KEY UPDATE code=VALUES(code), subject=VALUES(subject), internal_date=VALUES(internal_date)";
    $st = $pdo->prepare($sql);
    $st->execute([$imap_id, $uid, $platform_id, $account_email, $subject, $dt, $code]);
    return;
  } catch (Throwable $e) {
    // fallback
  }
  try {
    $sql = "INSERT INTO codes_log (client_id, platform_id, account_email, code, subject, uid, msg_date, fetched_at)
            VALUES (?,?,?,?,?,?,?,NOW())";
    $st = $pdo->prepare($sql);
    $st->execute([0, $platform_id, $account_email, $code, $subject, $uid, $dt]);
  } catch (Throwable $e) {
    // ignore
  }
}

// -------------------- Fetchers --------------------

/**
 * COMPAT MODE: mirrors the simple working behavior
 * - INBOX only
 * - UNSEEN first; else last by arrival
 * - subject must contain one of the phrases
 * - code = first 6-digit in BODY (or spaced)
 */
function fetch_codes_for_client_compat(int $client_id, int $platform_id, string $account_email, int $minutes = 5): array {
  $imap = get_imap_for_account($client_id, $platform_id, $account_email);
  if (!$imap) return [];

  $mbox = imap_mailbox_string($imap) . 'INBOX';
  $stream = @imap_open($mbox, $imap['username'], decrypt_text($imap['password_enc']), 0, 1, ['DISABLE_AUTHENTICATOR'=>'GSSAPI']);
  if (!$stream) return [];

  $uids = @imap_search($stream, 'UNSEEN', SE_UID, 'UTF-8');
  if (!$uids) {
    $sorted = @imap_sort($stream, SORTARRIVAL, 1, SE_UID) ?: [];
    $uids = array_slice(array_reverse($sorted), 0, 30);
  } else {
    $uids = array_slice(array_reverse($uids), 0, 30);
  }

  $phrases = get_phrases($platform_id);
  $regexes = get_regexes($platform_id);
  $now = time();
  $out = [];

  foreach ($uids as $uid) {
    $ov = @imap_fetch_overview($stream, (string)$uid, FT_UID);
    if (!$ov || empty($ov[0])) continue;
    $o = $ov[0];
    $subject = isset($o->subject) ? imap_utf8($o->subject) : '';
    $dateTs = strtotime($o->date ?? '');
    // allow a bit older but prefer recent later
    $hit = false;
    foreach ($phrases as $ph) {
      if ($ph && stripos($subject, (string)$ph) !== false) { $hit = true; break; }
    }
    if (!$hit) continue;

    $body = imap_get_text($stream, (int)$uid);
    $best = pick_best_code($body, $phrases, $regexes);
    if (!$best) {
      // fallback: plain extraction
      $list = extract_codes($body, $regexes);
      $best = $list[0] ?? '';
    }
    if (!$best) continue;

    $out[] = [
      'uid' => (int)$uid,
      'subject' => $subject,
      'code' => $best,
      'date' => $o->date ?? '',
      'from' => isset($o->from) ? imap_utf8($o->from) : '',
    ];

    upsert_code_message_simple((int)$imap['id'], $platform_id, $account_email, (int)$uid, $subject, $best, $o->date ?? '');
  }

  imap_close($stream);

  usort($out, function($a,$b){
    return strtotime($b['date'] ?? 'now') <=> strtotime($a['date'] ?? 'now');
  });
  return $out;
}

/** Public API used by the app */
function fetch_codes_for_client(int $client_id, int $platform_id, string $account_email, int $minutes = 5): array {
  if (defined('IMAP_MODE') && IMAP_MODE === 'compat') {
    return fetch_codes_for_client_compat($client_id, $platform_id, $account_email, $minutes);
  }
  // For now delegate to compat as default; strict mode can be re-enabled later
  return fetch_codes_for_client_compat($client_id, $platform_id, $account_email, $minutes);
}
