<?php

/**
 * Part of the Joomla Framework Database Package
 *
 * @copyright  Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Database\Command;

use Joomla\Archive\Archive;
use Joomla\Archive\Zip;
use Joomla\Console\Command\AbstractCommand;
use Joomla\Database\DatabaseDriver;
use Joomla\Database\Exception\UnsupportedAdapterException;
use Joomla\Filesystem\File;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
 * Console command for exporting the database
 *
 * @since  2.0.0
 */
class ExportCommand extends AbstractCommand
{
    /**
     * The default command name
     *
     * @var    string
     * @since  2.0.0
     */
    protected static $defaultName = 'database:export';

    /**
     * Database connector
     *
     * @var    DatabaseDriver
     * @since  2.0.0
     */
    private $db;

    /**
     * Instantiate the command.
     *
     * @param   DatabaseDriver  $db  Database connector
     *
     * @since   2.0.0
     */
    public function __construct(DatabaseDriver $db)
    {
        $this->db = $db;

        parent::__construct();
    }

    /**
     * Internal function to execute the command.
     *
     * @param   InputInterface   $input   The input to inject into the command.
     * @param   OutputInterface  $output  The output to inject into the command.
     *
     * @return  integer  The command exit code
     *
     * @since   2.0.0
     */
    protected function doExecute(InputInterface $input, OutputInterface $output): int
    {
        $symfonyStyle = new SymfonyStyle($input, $output);

        $symfonyStyle->title('Exporting Database');

        $totalTime = microtime(true);

        if (!class_exists(File::class)) {
            $symfonyStyle->error('The "joomla/filesystem" Composer package is not installed, cannot create an export.');

            return 1;
        }

        // Make sure the database supports exports before we get going
        try {
            $exporter = $this->db->getExporter()
                ->withStructure();
        } catch (UnsupportedAdapterException $e) {
            $symfonyStyle->error(sprintf('The "%s" database driver does not support exporting data.', $this->db->getName()));

            return 1;
        }

        $folderPath = $input->getOption('folder');
        $tableName  = $input->getOption('table');
        $zip        = $input->getOption('zip');

        $tables  = $this->db->getTableList();
        $prefix  = $this->db->getPrefix();

        if ($tableName) {
            if (!\in_array($tableName, $tables)) {
                $symfonyStyle->error(sprintf('The %s table does not exist in the database.', $tableName));

                return 1;
            }

            $tables = [$tableName];
        }

        if ($zip && !class_exists(Archive::class)) {
            $symfonyStyle->error('The "joomla/archive" Composer package is not installed, cannot create ZIP files.');

            return 1;
        }

        $filenames = [];
        $zipFilesArray = [];

        foreach ($tables as $table) {
            // If an empty prefix is in use then we will dump all tables, otherwise the prefix must match
            if (strlen($prefix) === 0 || strpos(substr($table, 0, strlen($prefix)), $prefix) !== false) {
                $taskTime = microtime(true);
                $filename = $folderPath . '/' . $table . '.xml';

                $symfonyStyle->text(sprintf('Processing the %s table', $table));

                $data = (string) $exporter->from($table)->withData(true);

                if (file_exists($filename)) {
                    File::delete($filename);
                }

                File::write($filename, $data);

                $zipFilesArray[] = ['name' => $table . '.xml', 'data' => $data];
                $filenames[] = $filename;

                $symfonyStyle->text(sprintf('Exported data for %s in %d seconds', $table, round(microtime(true) - $taskTime, 3)));
            }
        }

        if ($zip) {
            /** @var Zip $zipArchive */
            $zipArchive = (new Archive())->getAdapter('zip');

            $zipFile = $folderPath . '/data_exported_' . date("Y-m-d\TH-i-s") . '.zip';
            $zipArchive->create($zipFile, $zipFilesArray);
            foreach ($filenames as $fname) {
                File::delete($fname);
            }
        }

        $symfonyStyle->success(sprintf('Export completed in %d seconds', round(microtime(true) - $totalTime, 3)));

        return 0;
    }

    /**
     * Configure the command.
     *
     * @return  void
     *
     * @since   2.0.0
     */
    protected function configure(): void
    {
        $this->setDescription('Export the database');
        $this->addOption('folder', null, InputOption::VALUE_OPTIONAL, 'Path to write the export files to', '.');
        $this->addOption('table', null, InputOption::VALUE_REQUIRED, 'The name of the database table to export');
        $this->addOption('zip', null, InputOption::VALUE_NONE, 'Flag indicating the export will be saved to a ZIP archive');
    }
}
