<?php

namespace Roundcube\Tests;

use PHPUnit\Framework\TestCase;

/**
 * Test class to test rcmail_action_mail_index
 */
class ActionTestCase extends TestCase
{
    private static $files = [];

    #[\Override]
    public static function setUpBeforeClass(): void
    {
        // reset some interfering globals set in other tests
        $_SERVER['REQUEST_URI'] = '';

        $rcmail = \rcmail::get_instance();
        $rcmail->load_gui();
    }

    #[\Override]
    public static function tearDownAfterClass(): void
    {
        foreach (self::$files as $file) {
            unlink($file);
        }

        self::$files = [];

        $rcmail = \rcmail::get_instance();
        $rcmail->shutdown();

        \html::$doctype = 'xhtml';

        $_FILES = [];
    }

    #[\Override]
    protected function setUp(): void
    {
        $_GET = [];
        $_POST = [];
        $_REQUEST = [];
    }

    /**
     * Initialize the testing suite
     */
    public static function init()
    {
        self::initSession();
        self::initDB();
        self::initUser();
        self::mockStorage();
    }

    /**
     * Initialize "mocked" output class
     */
    protected static function initOutput($mode, $task, $action, $framed = false)
    {
        $rcmail = \rcmail::get_instance();

        $rcmail->task = $task;
        $rcmail->action = $action;

        if ($mode == \rcmail_action::MODE_AJAX) {
            return $rcmail->output = new OutputJsonMock();
        }

        $rcmail->output = new OutputHtmlMock($task, $framed);

        if ($framed) {
            $rcmail->comm_path .= '&_framed=1';
            $rcmail->output->set_env('framed', true);
        }

        $rcmail->output->set_env('task', $task);
        $rcmail->output->set_env('action', $action);
        $rcmail->output->set_env('comm_path', $rcmail->comm_path);
        $rcmail->output->set_charset(RCUBE_CHARSET);

        return $rcmail->output;
    }

    /**
     * Wipe and re-initialize database
     */
    public static function initDB($file = null)
    {
        $rcmail = \rcmail::get_instance();
        $dsn = \rcube_db::parse_dsn($rcmail->config->get('db_dsnw'));
        $db = $rcmail->get_dbh();

        if ($file) {
            self::loadSQLScript($db, $file);
            return;
        }

        if ($dsn['phptype'] == 'mysql' || $dsn['phptype'] == 'mysqli') {
            // drop all existing tables first
            $db->query('SET FOREIGN_KEY_CHECKS=0');
            $sql_res = $db->query('SHOW TABLES');
            while ($sql_arr = $db->fetch_array($sql_res)) {
                $table = reset($sql_arr);
                $db->query("DROP TABLE {$table}");
            }

            // init database with schema
            system(sprintf('cat %s %s | mysql -h %s -u %s --password=%s %s',
                realpath(INSTALL_PATH . '/SQL/mysql.initial.sql'),
                realpath(TESTS_DIR . 'src/sql/init.sql'),
                escapeshellarg($dsn['hostspec']),
                escapeshellarg($dsn['username']),
                escapeshellarg($dsn['password']),
                escapeshellarg($dsn['database'])
            ));
        } elseif ($dsn['phptype'] == 'sqlite') {
            $db->closeConnection();

            // delete database file
            @unlink($dsn['database']);

            // load sample test data
            self::loadSQLScript($db, 'init');
        }
    }

    /**
     * Set the $rcmail->user property
     */
    public static function initUser()
    {
        $rcmail = \rcmail::get_instance();
        $rcmail->set_user(new \rcube_user(1));
    }

    /**
     * Set the $rcmail->session property
     */
    public static function initSession()
    {
        $rcmail = \rcmail::get_instance();
        $rcmail->session = new \rcube_session_php($rcmail->config);
    }

    /**
     * Set the $rcmail->storage property
     *
     * @return StorageMock The storage object
     */
    public static function mockStorage()
    {
        $rcmail = \rcmail::get_instance();
        $rcmail->storage = new StorageMock(); // @phpstan-ignore-line

        return $rcmail->storage;
    }

    /**
     * Create a temp file
     */
    protected function createTempFile($content = '')
    {
        $file = \rcube_utils::temp_filename('tests');

        if ($content !== '') {
            file_put_contents($file, $content);
        }

        self::$files[] = $file;

        return $file;
    }

    /**
     * Prepare fake file upload handling
     */
    protected function fakeUpload($name = '_file', $is_array = true, $error = 0)
    {
        $content = base64_decode(\rcmail_output::BLANK_GIF);
        $file = [
            'name' => 'test.gif',
            'type' => 'image/gif',
            'tmp_name' => $this->createTempFile($content),
            'error' => $error,
            'size' => strlen($content),
            'id' => 'i' . microtime(true),
        ];

        // Attachments handling plugins use move_uploaded_file() which does not work
        // here. We'll add a fake hook handler for our purposes.
        $rcmail = \rcmail::get_instance();
        $rcmail->plugins->register_hook('attachment_upload', static function ($att) use ($file) {
            $att['status'] = true;
            $att['id'] = $file['id'];
            return $att;
        });

        $_FILES = [];

        if ($is_array) {
            $_FILES[$name] = [
                'name' => [$file['name']],
                'type' => [$file['type']],
                'tmp_name' => [$file['tmp_name']],
                'error' => [$file['error']],
                'size' => [$file['size']],
                'id' => [$file['id']],
            ];
        } else {
            $_FILES[$name] = $file;
        }

        return $file;
    }

    /**
     * Create file upload record
     */
    protected function fileUpload($group)
    {
        $content = base64_decode(\rcmail_output::BLANK_GIF);
        $file = [
            'name' => 'test.gif',
            'type' => 'image/gif',
            'size' => strlen($content),
            'group' => $group,
            'id' => 'i' . microtime(true),
        ];

        // Attachments handling plugins use move_uploaded_file() which does not work
        // here. We'll add a fake hook handler for our purposes.
        $rcmail = \rcmail::get_instance();
        $rcmail->plugins->register_hook('attachment_upload', static function ($att) use ($file) {
            $att['status'] = true;
            $att['id'] = $file['id'];
            return $att;
        });

        $rcmail->insert_uploaded_file($file);

        $upload = \rcmail::get_instance()->get_uploaded_file($file['id']);

        $this->assertTrue(is_array($upload));

        return $upload;
    }

    /**
     * Load an execute specified SQL script
     */
    protected static function loadSQLScript($db, $name)
    {
        // load sample test data
        // Note: exec_script() does not really work with these queries
        $sql = file_get_contents(TESTS_DIR . "src/sql/{$name}.sql");
        $sql = preg_split('/;\n/', $sql, -1, \PREG_SPLIT_NO_EMPTY);

        foreach ($sql as $query) {
            $result = $db->query($query);
            if ($error = $db->is_error($result)) {
                \rcube::raise_error($error, false, true);
            }
        }
    }

    /**
     * Call the action's run() method and handle exit exception
     */
    protected function runAndAssert($action, $expected_code, $args = [])
    {
        // Reset output in case we execute the method multiple times in a single test
        $rcmail = \rcmail::get_instance();
        $rcmail->output->reset(true);

        // reset some static props
        setProperty($action, 'edit_form', null);

        try {
            StderrMock::start();
            $action->run($args);
            StderrMock::stop();
        } catch (ExitException $e) {
            $this->assertSame($expected_code, $e->getCode());
        } catch (\Exception $e) {
            if ($e->getMessage() == 'Error raised' && $expected_code == OutputHtmlMock::E_EXIT) {
                return;
            }

            echo StderrMock::$output;
            throw $e;
        }
    }
}
