<?php
/**
 * includes/admin/import-archive.php
 *
 * ============================================================================
 * ARQUIVO DE CSVs IMPORTADOS — para permitir desfazer anulações
 * ============================================================================
 *
 * Quando um import é commitado, o CSV original é movido para
 * /uploads/rmbz_imports_archive/ em vez de ser apagado. Isto permite:
 *
 *   - Re-processar a partir do CSV original se admin anular por engano
 *   - Auditoria forense (ver o ficheiro exato que foi importado)
 *   - Recuperação após bugs (ex: 1.5.103 quando rejeitar/anular afetava
 *     dados de imports que partilhavam file_hash)
 *
 * Segurança:
 *   - .htaccess bloqueia acesso directo via web (Apache)
 *   - Para Nginx, recomenda-se bloqueio na config do servidor
 *   - Nome do ficheiro é prefixado com import_log_id para ligação directa
 *   - Apenas WordPress admin pode aceder (via PHP)
 *
 * Limpeza:
 *   - Por design, NÃO há limpeza automática (admin decide)
 *   - Comando manual disponível via página de Diagnóstico
 *   - Tamanho típico: ~50KB-2MB por CSV
 */

if ( ! defined( 'ABSPATH' ) ) exit;


/**
 * Devolve o path absoluto da pasta de arquivo, criando-a se necessário.
 * Retorna string vazia se a criação falhar.
 */
function rmbz_imports_archive_dir() {
    $upload_dir = wp_upload_dir();
    if ( ! empty($upload_dir['error']) ) {
        return '';
    }
    $archive_dir = $upload_dir['basedir'] . '/rmbz_imports_archive';

    if ( ! file_exists($archive_dir) ) {
        if ( ! wp_mkdir_p($archive_dir) ) {
            error_log('[1MZ archive] Falhou criar pasta: ' . $archive_dir);
            return '';
        }
        // .htaccess para bloquear acesso web (Apache)
        @file_put_contents(
            $archive_dir . '/.htaccess',
            "Order Deny,Allow\nDeny from all\n"
        );
        // index.php em branco para evitar listagem em servidores que ignoram .htaccess
        @file_put_contents(
            $archive_dir . '/index.php',
            "<?php\n// Silence is golden.\n"
        );
    }

    return $archive_dir;
}


/**
 * Arquiva o ficheiro CSV de uma importação. Move (não copia) para poupar
 * I/O. Devolve o path final do ficheiro arquivado, ou string vazia se
 * o arquivamento falhou (mas o caller deve continuar — falha no arquivo
 * não deve abortar o import).
 *
 * @param string $source_path     Path actual do CSV (geralmente /tmp_imports/)
 * @param int    $import_log_id   ID da linha em rmbz_distro_import_logs
 * @param string $file_hash       Hash do ficheiro (curto)
 * @return string                 Path final ou ''
 */
function rmbz_archive_import_csv( $source_path, $import_log_id, $file_hash ) {
    if ( empty($source_path) || ! file_exists($source_path) ) {
        return '';
    }
    $archive_dir = rmbz_imports_archive_dir();
    if ( empty($archive_dir) ) {
        return '';
    }

    // Nome final: <id>_<hash8>_<basename>
    // O id permite ligação directa, o hash é redundância para auditoria
    $original = sanitize_file_name( basename($source_path) );
    $hash8    = substr( $file_hash, 0, 8 );
    $final_name = sprintf( '%d_%s_%s', $import_log_id, $hash8, $original );
    $final_path = $archive_dir . '/' . $final_name;

    // Tentar mover (rename é atómico no mesmo filesystem)
    if ( @rename( $source_path, $final_path ) ) {
        return $final_path;
    }

    // Se rename falhou (cross-filesystem), tentar copy + unlink
    if ( @copy( $source_path, $final_path ) ) {
        @unlink( $source_path );
        return $final_path;
    }

    error_log('[1MZ archive] Falhou arquivar ' . $source_path . ' -> ' . $final_path);
    return '';
}


/**
 * Verifica se um CSV arquivado ainda existe no disco. Útil antes de
 * mostrar botão "Desfazer anulação".
 */
function rmbz_archive_csv_exists( $archived_path ) {
    return ! empty($archived_path) && file_exists($archived_path) && is_readable($archived_path);
}


/**
 * Devolve o tamanho total da pasta de arquivo em bytes (para mostrar
 * no diagnóstico). Limita scan a 1000 ficheiros para evitar lentidão.
 */
function rmbz_archive_total_size() {
    $archive_dir = rmbz_imports_archive_dir();
    if ( empty($archive_dir) ) return 0;
    $total = 0;
    $count = 0;
    foreach ( glob( $archive_dir . '/*.csv' ) as $f ) {
        if ( ++$count > 1000 ) break;
        $total += @filesize($f) ?: 0;
    }
    return $total;
}


/**
 * [DS 1.5.109] Devolve o número de dias de retenção configurado.
 * Default 730 (2 anos). Admin pode mudar via option `rmbz_csv_archive_retention_days`.
 * Mínimo aceite: 30 dias (proteção contra purga acidental imediata).
 */
function rmbz_archive_retention_days() {
    $days = (int) get_option( 'rmbz_csv_archive_retention_days', 730 );
    if ( $days < 30 ) $days = 30;
    return $days;
}


/**
 * [DS 1.5.109] Pré-visualiza quais CSVs seriam apagados pela próxima
 * purga, sem realmente apagar nada. Usado pela UI antes de admin
 * confirmar a limpeza manual.
 *
 * @return array {
 *   'count'        => int,
 *   'total_bytes'  => int,
 *   'cutoff_date'  => string YYYY-MM-DD,
 *   'sample'       => array (até 5 entradas para preview com hash, period, age_days)
 * }
 */
function rmbz_archive_preview_purge() {
    global $wpdb;
    $t_logs = $wpdb->prefix . 'rmbz_distro_import_logs';
    $days   = rmbz_archive_retention_days();
    $cutoff = gmdate( 'Y-m-d H:i:s', time() - ($days * DAY_IN_SECONDS) );

    $rows = $wpdb->get_results( $wpdb->prepare(
        "SELECT id, file_hash, reporting_period, archived_path, imported_at,
                DATEDIFF(NOW(), imported_at) AS age_days
         FROM $t_logs
         WHERE archived_path IS NOT NULL
           AND archived_path != ''
           AND archive_purged_at IS NULL
           AND imported_at < %s
         ORDER BY imported_at ASC",
        $cutoff
    ) );

    $count = 0;
    $total_bytes = 0;
    $sample = [];
    foreach ( $rows as $r ) {
        if ( ! file_exists($r->archived_path) ) continue;
        $count++;
        $total_bytes += (int) @filesize($r->archived_path);
        if ( count($sample) < 5 ) {
            $sample[] = [
                'id'         => (int) $r->id,
                'hash'       => substr($r->file_hash, 0, 12),
                'period'     => $r->reporting_period,
                'age_days'   => (int) $r->age_days,
                'imported'   => $r->imported_at,
                'size_bytes' => (int) @filesize($r->archived_path),
            ];
        }
    }

    return [
        'count'       => $count,
        'total_bytes' => $total_bytes,
        'cutoff_date' => substr($cutoff, 0, 10),
        'retention_days' => $days,
        'sample'      => $sample,
    ];
}


/**
 * [DS 1.5.109] Executa a purga real de CSVs antigos. Apaga apenas o
 * ficheiro físico. Mantém todos os dados em rmbz_distro_import_logs
 * (processed_rows, total_amount_usd, etc.) para auditoria histórica.
 * Marca `archive_purged_at` para sabermos quando foi limpo.
 *
 * @param int  $limit          Máximo de ficheiros a apagar (default 500 para
 *                             evitar timeouts em servidores lentos).
 * @return array { 'deleted'=>int, 'bytes_freed'=>int, 'errors'=>int }
 */
function rmbz_archive_purge_old( $limit = 500 ) {
    global $wpdb;
    $t_logs = $wpdb->prefix . 'rmbz_distro_import_logs';
    $days   = rmbz_archive_retention_days();
    $cutoff = gmdate( 'Y-m-d H:i:s', time() - ($days * DAY_IN_SECONDS) );

    $rows = $wpdb->get_results( $wpdb->prepare(
        "SELECT id, archived_path
         FROM $t_logs
         WHERE archived_path IS NOT NULL
           AND archived_path != ''
           AND archive_purged_at IS NULL
           AND imported_at < %s
         ORDER BY imported_at ASC
         LIMIT %d",
        $cutoff, (int) $limit
    ) );

    $deleted = 0;
    $bytes_freed = 0;
    $errors = 0;
    $now = current_time('mysql');

    foreach ( $rows as $r ) {
        if ( file_exists($r->archived_path) && is_writable($r->archived_path) ) {
            $size = (int) @filesize($r->archived_path);
            if ( @unlink($r->archived_path) ) {
                $deleted++;
                $bytes_freed += $size;
                // Marcar na BD que foi limpo (mantém o path para histórico)
                $wpdb->update(
                    $t_logs,
                    [ 'archive_purged_at' => $now ],
                    [ 'id' => (int) $r->id ],
                    [ '%s' ], [ '%d' ]
                );
            } else {
                $errors++;
                error_log('[1MZ archive_purge] Falhou apagar ' . $r->archived_path);
            }
        } else {
            // Ficheiro já não existe (pode ter sido apagado manualmente)
            // — marcar mesmo assim para parar de aparecer na lista
            $wpdb->update(
                $t_logs,
                [ 'archive_purged_at' => $now ],
                [ 'id' => (int) $r->id ],
                [ '%s' ], [ '%d' ]
            );
        }
    }

    return [
        'deleted'      => $deleted,
        'bytes_freed'  => $bytes_freed,
        'errors'       => $errors,
        'retention_days' => $days,
    ];
}


/**
 * [DS 1.5.109] Hook de cron diário. Corre purga sem confirmação humana,
 * limita a 200 ficheiros por execução para não saturar servidores lentos.
 * Erros silenciosos vão para error_log.
 */
function rmbz_archive_cron_purge() {
    $result = rmbz_archive_purge_old( 200 );
    if ( $result['deleted'] > 0 || $result['errors'] > 0 ) {
        error_log( sprintf(
            '[1MZ archive_cron] Apagados %d ficheiro(s) (%s libertados, %d erros)',
            $result['deleted'],
            size_format( $result['bytes_freed'] ),
            $result['errors']
        ) );
    }
}
add_action( 'rmbz_archive_daily_purge', 'rmbz_archive_cron_purge' );


/**
 * [DS 1.5.109] Agendar o cron na activação do plugin (corre 1x/dia às 03h00).
 * Hook a admin_init garante que está sempre agendado mesmo se for criado
 * post-activação.
 */
function rmbz_archive_ensure_cron_scheduled() {
    if ( ! wp_next_scheduled( 'rmbz_archive_daily_purge' ) ) {
        // Próxima ocorrência às 03h00 (servidor) — fora dos picos de tráfego
        $next = strtotime( 'tomorrow 03:00' );
        wp_schedule_event( $next, 'daily', 'rmbz_archive_daily_purge' );
    }
}
add_action( 'admin_init', 'rmbz_archive_ensure_cron_scheduled' );
