<?php
/**
 * FEC (Fichier des Écritures Comptables) export.
 *
 * Format required by the French tax administration in case of a digital tax audit
 * (article A47 A-1 of the LPF). 18 columns, tab-separated, encoded in ISO-8859-15
 * or UTF-8 with BOM. We emit UTF-8 with BOM for maximum compatibility with modern
 * accounting software.
 *
 * Each invoice produces one accounting entry composed of two lines:
 *   - Debit account 411xxx (clients) for the gross amount
 *   - Credit account 707xxx (sales) for the net amount + 44571xxx (VAT collected) per rate
 *
 * Each credit note produces the reverse entries (positive amounts; the FEC structure
 * preserves debit/credit semantics via the column placements, not negative signs).
 *
 * Reference: https://bofip.impots.gouv.fr/bofip/9028-PGP.html
 *
 * @package FacturX_For_WooCommerce
 */

defined( 'ABSPATH' ) || exit;

class GOFACTOX_FEC_Export {

    private const NONCE_ACTION = 'gofactox_fec_export';

    public static function init(): void {
        add_action( 'admin_post_gofactox_fec_export', [ __CLASS__, 'handle_export' ] );
    }

    /**
     * Build the export form URL with nonce + date range.
     */
    public static function get_export_url( string $from = '', string $to = '' ): string {
        return wp_nonce_url(
            add_query_arg(
                [
                    'action' => 'gofactox_fec_export',
                    'from'   => $from,
                    'to'     => $to,
                ],
                admin_url( 'admin-post.php' )
            ),
            self::NONCE_ACTION
        );
    }

    /**
     * Handle the export request.
     */
    public static function handle_export(): void {
        if ( ! current_user_can( 'manage_woocommerce' ) ) {
            wp_die( esc_html__( 'Permission denied.', 'gofactox-e-invoicing-for-woocommerce' ), 403 );
        }
        if ( ! GOFACTOX_License::is_business() ) {
            wp_die( esc_html__( 'FEC export is available on the Business plan.', 'gofactox-e-invoicing-for-woocommerce' ), 402 );
        }
        check_admin_referer( self::NONCE_ACTION );

        $from = isset( $_GET['from'] ) ? sanitize_text_field( wp_unslash( $_GET['from'] ) ) : '';
        $to   = isset( $_GET['to'] ) ? sanitize_text_field( wp_unslash( $_GET['to'] ) ) : '';

        $rows = self::collect_rows( $from, $to );

        $siren    = substr( (string) get_option( 'gofactox_siret', '' ), 0, 9 ) ?: 'SIREN';
        $period   = ( $from ?: 'all' ) . '_to_' . ( $to ?: 'now' );
        $filename = $siren . 'FEC' . sanitize_file_name( $period ) . '.txt';

        header( 'Content-Type: text/plain; charset=UTF-8' );
        header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
        header( 'Cache-Control: no-cache, must-revalidate' );

        // UTF-8 BOM for Excel and legacy tools.
        echo "\xEF\xBB\xBF";

        // Header row (A47 A-1).
        $columns = self::columns();
        // The response is plain text/CSV (text/plain Content-Type), not HTML. Individual
        // fields are scrubbed of tabs/newlines below to prevent column injection.
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
        echo implode( "\t", $columns ) . "\n";

        foreach ( $rows as $row ) {
            $ordered = [];
            foreach ( $columns as $col ) {
                $ordered[] = isset( $row[ $col ] ) ? str_replace( [ "\t", "\n", "\r" ], ' ', (string) $row[ $col ] ) : '';
            }
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
            echo implode( "\t", $ordered ) . "\n";
        }
        exit;
    }

    /**
     * The 18 standard FEC columns in the legal order.
     */
    private static function columns(): array {
        return [
            'JournalCode', 'JournalLib', 'EcritureNum', 'EcritureDate',
            'CompteNum', 'CompteLib', 'CompAuxNum', 'CompAuxLib',
            'PieceRef', 'PieceDate', 'EcritureLib',
            'Debit', 'Credit', 'EcritureLet', 'DateLet',
            'ValidDate', 'Montantdevise', 'Idevise',
        ];
    }

    /**
     * Collect accounting entries from all invoices + credit notes in the range.
     *
     * @return array<int, array<string, string>>
     */
    private static function collect_rows( string $from, string $to ): array {
        $args = [
            'post_type'      => GOFACTOX_Invoice::POST_TYPE,
            'post_status'    => 'publish',
            'posts_per_page' => -1,
            'orderby'        => 'date',
            'order'          => 'ASC',
        ];

        if ( $from || $to ) {
            $date_query = [];
            if ( $from ) {
                $date_query['after'] = $from;
            }
            if ( $to ) {
                $date_query['before'] = $to;
            }
            $date_query['inclusive'] = true;
            $args['date_query']      = [ $date_query ];
        }

        $posts = get_posts( $args );
        $rows  = [];
        $seq   = 0;

        foreach ( $posts as $post ) {
            $invoice_id    = $post->ID;
            $is_credit     = GOFACTOX_Invoice::is_credit_note( $invoice_id );
            $number        = (string) get_post_meta( $invoice_id, '_gofactox_invoice_number', true );
            $date          = (string) get_post_meta( $invoice_id, '_gofactox_invoice_date', true );
            $total_ht      = (float) get_post_meta( $invoice_id, '_gofactox_total_ht', true );
            $total_tax     = (float) get_post_meta( $invoice_id, '_gofactox_total_tax', true );
            $total_ttc     = (float) get_post_meta( $invoice_id, '_gofactox_total_ttc', true );
            $currency      = (string) get_post_meta( $invoice_id, '_gofactox_currency', true );
            $order_id      = (int) get_post_meta( $invoice_id, '_gofactox_order_id', true );

            $order = $order_id ? wc_get_order( $order_id ) : null;
            $buyer = ( $order instanceof \WC_Order )
                ? trim( ( $order->get_billing_company() ?: ( $order->get_billing_first_name() . ' ' . $order->get_billing_last_name() ) ) )
                : '';

            $seq++;
            $ecriture_num = sprintf( 'EC%05d', $seq );
            $journal_code = $is_credit ? 'VEAV' : 'VE';
            $journal_lib  = $is_credit ? 'Avoirs' : 'Ventes';
            $piece_date   = self::format_date( $date );
            $ecriture_lib = ( $is_credit ? 'Avoir ' : 'Facture ' ) . $number;

            // Line 1: customer account 411 (debit for invoice, credit for credit note).
            $rows[] = self::row(
                $journal_code, $journal_lib, $ecriture_num, $piece_date,
                '411000', 'Clients', 'C' . $order_id, $buyer,
                $number, $piece_date, $ecriture_lib,
                $is_credit ? 0.0 : $total_ttc,
                $is_credit ? $total_ttc : 0.0,
                $currency
            );

            // Line 2: sales account 707 (credit for invoice, debit for credit note).
            $rows[] = self::row(
                $journal_code, $journal_lib, $ecriture_num, $piece_date,
                '707000', 'Ventes', '', '',
                $number, $piece_date, $ecriture_lib,
                $is_credit ? $total_ht : 0.0,
                $is_credit ? 0.0 : $total_ht,
                $currency
            );

            // Line 3: VAT collected 44571 (credit for invoice, debit for credit note).
            // Only if there is VAT collected.
            if ( $total_tax > 0 ) {
                $rows[] = self::row(
                    $journal_code, $journal_lib, $ecriture_num, $piece_date,
                    '445710', 'TVA collectee', '', '',
                    $number, $piece_date, $ecriture_lib,
                    $is_credit ? $total_tax : 0.0,
                    $is_credit ? 0.0 : $total_tax,
                    $currency
                );
            }
        }

        return $rows;
    }

    private static function row(
        string $journal_code, string $journal_lib, string $ecriture_num, string $ecriture_date,
        string $compte_num, string $compte_lib, string $compaux_num, string $compaux_lib,
        string $piece_ref, string $piece_date, string $ecriture_lib,
        float $debit, float $credit, string $currency
    ): array {
        return [
            'JournalCode'   => $journal_code,
            'JournalLib'    => $journal_lib,
            'EcritureNum'   => $ecriture_num,
            'EcritureDate'  => $ecriture_date,
            'CompteNum'     => $compte_num,
            'CompteLib'     => $compte_lib,
            'CompAuxNum'    => $compaux_num,
            'CompAuxLib'    => $compaux_lib,
            'PieceRef'      => $piece_ref,
            'PieceDate'     => $piece_date,
            'EcritureLib'   => $ecriture_lib,
            'Debit'         => number_format( $debit, 2, ',', '' ),
            'Credit'        => number_format( $credit, 2, ',', '' ),
            'EcritureLet'   => '',
            'DateLet'       => '',
            'ValidDate'     => $ecriture_date,
            'Montantdevise' => '',
            'Idevise'       => $currency !== 'EUR' ? $currency : '',
        ];
    }

    private static function format_date( string $ymd ): string {
        $d = \DateTime::createFromFormat( 'Y-m-d', $ymd );
        return $d ? $d->format( 'Ymd' ) : '';
    }
}
