<?php
declare(strict_types=1);

namespace Itdatex\EInvoiceCore;

/**
 * Idempotentes Setup der wp_reing_*-Tabellen.
 *
 * Single Source of Truth fuer das DB-Schema, das Eingangs- und
 * Ausgangs-Plugin gemeinsam nutzen. Beide Plugins rufen ensureSchema()
 * im Activation-Hook; dbDelta ist idempotent, kollidiert also nicht,
 * wenn der jeweils andere Installer bereits gelaufen ist.
 *
 * Schema-Aenderungen passieren AUSSCHLIESSLICH hier. Wer Spalten anfasst,
 * muss DB_VERSION hochziehen, damit beim naechsten Activate dbDelta die
 * Aenderungen anwendet.
 */
final class SchemaInstaller
{
    public const DB_VERSION_OPTION = 'reing_db_version';
    public const DB_VERSION        = '1';

    public static function ensureSchema(): void
    {
        global $wpdb;
        $charset  = $wpdb->get_charset_collate();
        $invoices = $wpdb->prefix . 'reing_invoices';
        $files    = $wpdb->prefix . 'reing_files';
        $audit    = $wpdb->prefix . 'reing_audit_log';

        require_once ABSPATH . 'wp-admin/includes/upgrade.php';

        // dbDelta-Eigenheiten: doppelte Leerzeichen vor PRIMARY KEY, KEY-
        // Definitionen am Ende, keine SQL-Kommentare in den DDL-Strings.
        dbDelta( "CREATE TABLE $invoices (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            file_id BIGINT UNSIGNED NOT NULL,
            direction VARCHAR(16) NOT NULL DEFAULT 'incoming',
            format VARCHAR(32) NOT NULL,
            invoice_number VARCHAR(190),
            issue_date DATE,
            due_date DATE,
            seller_name VARCHAR(190),
            seller_vat_id VARCHAR(32),
            buyer_name VARCHAR(190),
            buyer_reference VARCHAR(190),
            total_net DECIMAL(15,4),
            total_tax DECIMAL(15,4),
            total_gross DECIMAL(15,4),
            currency CHAR(3) DEFAULT 'EUR',
            line_count INT UNSIGNED NOT NULL DEFAULT 0,
            validation_status VARCHAR(16) NOT NULL DEFAULT 'unchecked',
            validation_errors INT UNSIGNED NOT NULL DEFAULT 0,
            validation_warnings INT UNSIGNED NOT NULL DEFAULT 0,
            validation_report LONGTEXT,
            payment_status VARCHAR(16) NOT NULL DEFAULT 'open',
            extracted_by VARCHAR(16) NOT NULL DEFAULT 'parser',
            raw_meta LONGTEXT,
            created_at DATETIME NOT NULL,
            updated_at DATETIME NOT NULL,
            PRIMARY KEY  (id),
            KEY file_id (file_id),
            KEY idx_due (payment_status, due_date),
            KEY idx_seller (seller_name(50))
        ) $charset;" );

        dbDelta( "CREATE TABLE $files (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            stored_name VARCHAR(190) NOT NULL,
            original_name VARCHAR(190) NOT NULL,
            mime VARCHAR(64),
            size_bytes BIGINT UNSIGNED,
            sha256 CHAR(64) NOT NULL,
            chain_hash CHAR(64) NOT NULL,
            source VARCHAR(16) NOT NULL DEFAULT 'upload',
            created_at DATETIME NOT NULL,
            PRIMARY KEY  (id),
            UNIQUE KEY uq_sha (sha256)
        ) $charset;" );

        dbDelta( "CREATE TABLE $audit (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            invoice_id BIGINT UNSIGNED,
            actor VARCHAR(64),
            action VARCHAR(64) NOT NULL,
            detail TEXT,
            created_at DATETIME NOT NULL,
            PRIMARY KEY  (id),
            KEY idx_inv (invoice_id)
        ) $charset;" );

        update_option( self::DB_VERSION_OPTION, self::DB_VERSION );
    }

    /**
     * Prueft, ob das gemeinsame Schema existiert. Nutzt SHOW TABLES, weil
     * INFORMATION_SCHEMA-Zugriffe auf Shared Hostings gerne mal eingeschraenkt
     * sind.
     */
    public static function schemaExists(): bool
    {
        global $wpdb;
        $table = $wpdb->prefix . 'reing_invoices';
        $found = (string) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
        return $found === $table;
    }

    /**
     * Legt einen geschuetzten Upload-Ordner mit Zufallssuffix unterhalb
     * wp-content/uploads/ an und speichert den Pfad in der angegebenen
     * Option. Idempotent: existiert das Verzeichnis schon, passiert nichts.
     *
     * Rueckgabe: Pfad des Upload-Ordners (oder Leerstring bei Fehler).
     */
    public static function ensureUploadDir(
        string $optionName = 'reing_upload_dir',
        string $dirPrefix  = 'itdatex-e-invoice-'
    ): string {
        $existing = (string) get_option( $optionName, '' );
        if ( $existing !== '' && is_dir( $existing ) ) {
            return $existing;
        }

        $uploads = wp_upload_dir();
        if ( ! empty( $uploads['error'] ) ) {
            return '';
        }

        try {
            $rand = bin2hex( random_bytes( 8 ) );
        } catch ( \Throwable ) {
            // random_bytes scheitert nur in pathologischen Faellen — Fallback
            $rand = substr( hash( 'sha256', uniqid( '', true ) ), 0, 16 );
        }
        $path = trailingslashit( $uploads['basedir'] ) . $dirPrefix . $rand;

        if ( ! wp_mkdir_p( $path ) ) {
            return '';
        }

        // Apache/LiteSpeed: per-Directory deny.
        // nginx: greift nicht; deshalb zusaetzlich Zufallsname als Schutz.
        @file_put_contents( $path . '/.htaccess', "Require all denied\nDeny from all\n" );
        @file_put_contents( $path . '/index.php', "<?php // Silence is golden.\n" );

        update_option( $optionName, $path );
        return $path;
    }
}
