<?php
declare(strict_types=1);

/**
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE
 * Redistributions of files must retain the above copyright notice
 *
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 * @link          https://cakephp.org CakePHP(tm) Project
 * @since         4.0.0
 * @license       https://opensource.org/licenses/mit-license.php MIT License
 */
namespace Cake\Test\TestCase\Mailer;

use Cake\Core\Configure;
use Cake\Mailer\Message;
use Cake\Mailer\Transport\DebugTransport;
use Cake\TestSuite\TestCase;
use Laminas\Diactoros\UploadedFile;
use TestApp\Mailer\TestMessage;

/**
 * MessageTest class
 */
class MessageTest extends TestCase
{
    /**
     * @var \Cake\Mailer\Message
     */
    protected $message;

    public function setUp(): void
    {
        parent::setUp();

        $this->message = new TestMessage();
    }

    /**
     * testWrap method
     *
     * @return void
     */
    public function testWrap()
    {
        $renderer = new TestMessage();

        $text = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ac turpis orci, non commodo odio. Morbi nibh nisi, vehicula pellentesque accumsan amet.';
        $result = $renderer->doWrap($text, Message::LINE_LENGTH_SHOULD);
        $expected = [
            'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ac turpis orci,',
            'non commodo odio. Morbi nibh nisi, vehicula pellentesque accumsan amet.',
            '',
        ];
        $this->assertSame($expected, $result);

        $text = 'Lorem ipsum dolor sit amet, consectetur < adipiscing elit. Donec ac turpis orci, non commodo odio. Morbi nibh nisi, vehicula > pellentesque accumsan amet.';
        $result = $renderer->doWrap($text, Message::LINE_LENGTH_SHOULD);
        $expected = [
            'Lorem ipsum dolor sit amet, consectetur < adipiscing elit. Donec ac turpis',
            'orci, non commodo odio. Morbi nibh nisi, vehicula > pellentesque accumsan',
            'amet.',
            '',
        ];
        $this->assertSame($expected, $result);

        $text = '<p>Lorem ipsum dolor sit amet,<br> consectetur adipiscing elit.<br> Donec ac turpis orci, non <b>commodo</b> odio. <br /> Morbi nibh nisi, vehicula pellentesque accumsan amet.<hr></p>';
        $result = $renderer->doWrap($text, Message::LINE_LENGTH_SHOULD);
        $expected = [
            '<p>Lorem ipsum dolor sit amet,<br> consectetur adipiscing elit.<br> Donec ac',
            'turpis orci, non <b>commodo</b> odio. <br /> Morbi nibh nisi, vehicula',
            'pellentesque accumsan amet.<hr></p>',
            '',
        ];
        $this->assertSame($expected, $result);

        $text = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ac <a href="http://cakephp.org">turpis</a> orci, non commodo odio. Morbi nibh nisi, vehicula pellentesque accumsan amet.';
        $result = $renderer->doWrap($text, Message::LINE_LENGTH_SHOULD);
        $expected = [
            'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ac',
            '<a href="http://cakephp.org">turpis</a> orci, non commodo odio. Morbi nibh',
            'nisi, vehicula pellentesque accumsan amet.',
            '',
        ];
        $this->assertSame($expected, $result);

        $text = 'Lorem ipsum <a href="http://www.cakephp.org/controller/action/param1/param2" class="nice cool fine amazing awesome">ok</a>';
        $result = $renderer->doWrap($text, Message::LINE_LENGTH_SHOULD);
        $expected = [
            'Lorem ipsum',
            '<a href="http://www.cakephp.org/controller/action/param1/param2" class="nice cool fine amazing awesome">',
            'ok</a>',
            '',
        ];
        $this->assertSame($expected, $result);

        $text = 'Lorem ipsum withonewordverybigMorethanthelineshouldsizeofrfcspecificationbyieeeavailableonieeesite ok.';
        $result = $renderer->doWrap($text, Message::LINE_LENGTH_SHOULD);
        $expected = [
            'Lorem ipsum',
            'withonewordverybigMorethanthelineshouldsizeofrfcspecificationbyieeeavailableonieeesite',
            'ok.',
            '',
        ];
        $this->assertSame($expected, $result);

        /** @see https://github.com/cakephp/cakephp/issues/14459 */
        $line = 'some text <b>with html</b>';
        $trailing = str_repeat('X', Message::LINE_LENGTH_MUST - strlen($line));
        $result = $renderer->doWrap($line . $trailing, Message::LINE_LENGTH_MUST);
        $expected = [
            'some text <b>with',
            'html</b>' . $trailing,
            '',
        ];
        $this->assertSame($expected, $result);
    }

    /**
     * testWrapLongLine()
     *
     * @return void
     */
    public function testWrapLongLine()
    {
        $transort = new DebugTransport();

        $message = '<a href="http://cakephp.org">' . str_repeat('x', Message::LINE_LENGTH_MUST) . '</a>';

        $this->message->setFrom('cake@cakephp.org');
        $this->message->setTo('cake@cakephp.org');
        $this->message->setSubject('Wordwrap Test');
        $this->message->setBodyText($message);

        $result = $transort->send($this->message);

        $expected = "<a\r\n" . 'href="http://cakephp.org">' . str_repeat('x', Message::LINE_LENGTH_MUST - 26) . "\r\n" .
            str_repeat('x', 26) . "\r\n</a>\r\n\r\n";
        $this->assertSame($expected, $result['message']);
        $this->assertLineLengths($result['message']);

        $str1 = 'a ';
        $str2 = ' b';
        $length = strlen($str1) + strlen($str2);
        $message = $str1 . str_repeat('x', Message::LINE_LENGTH_MUST - $length - 1) . $str2;

        $this->message->setBodyText($message);

        $result = $transort->send($this->message);
        $expected = "{$message}\r\n\r\n";
        $this->assertSame($expected, $result['message']);
        $this->assertLineLengths($result['message']);

        $message = $str1 . str_repeat('x', Message::LINE_LENGTH_MUST - $length) . $str2;

        $this->message->setBodyText($message);

        $result = $transort->send($this->message);
        $expected = "{$message}\r\n\r\n";
        $this->assertSame($expected, $result['message']);
        $this->assertLineLengths($result['message']);

        $message = $str1 . str_repeat('x', Message::LINE_LENGTH_MUST - $length + 1) . $str2;

        $this->message->setBodyText($message);

        $result = $transort->send($this->message);
        $expected = $str1 . str_repeat('x', Message::LINE_LENGTH_MUST - $length + 1) . sprintf("\r\n%s\r\n\r\n", trim($str2));
        $this->assertSame($expected, $result['message']);
        $this->assertLineLengths($result['message']);
    }

    /**
     * testWrapWithTagsAcrossLines()
     *
     * @return void
     */
    public function testWrapWithTagsAcrossLines()
    {
        $str = <<<HTML
<table>
<th align="right" valign="top"
        style="font-weight: bold">The tag is across multiple lines</th>
</table>
HTML;
        $message = $str . str_repeat('x', Message::LINE_LENGTH_MUST + 1);

        $this->message->setFrom('cake@cakephp.org');
        $this->message->setTo('cake@cakephp.org');
        $this->message->setSubject('Wordwrap Test');
        $this->message->setBodyText($message);

        $result = (new DebugTransport())->send($this->message);

        $message = str_replace("\r\n", "\n", substr($message, 0, -9));
        $message = str_replace("\n", "\r\n", $message);
        $expected = "{$message}\r\nxxxxxxxxx\r\n\r\n";
        $this->assertSame($expected, $result['message']);
        $this->assertLineLengths($result['message']);
    }

    /**
     * CakeEmailTest::testWrapIncludeLessThanSign()
     *
     * @return void
     */
    public function testWrapIncludeLessThanSign()
    {
        $str = 'foo<bar';
        $length = strlen($str);
        $message = $str . str_repeat('x', Message::LINE_LENGTH_MUST - $length + 1);

        $this->message->setFrom('cake@cakephp.org');
        $this->message->setTo('cake@cakephp.org');
        $this->message->setSubject('Wordwrap Test');
        $this->message->setBodyText($message);

        $result = (new DebugTransport())->send($this->message);
        $message = substr($message, 0, -1);
        $expected = "{$message}\r\nx\r\n\r\n";
        $this->assertSame($expected, $result['message']);
        $this->assertLineLengths($result['message']);
    }

    /**
     * CakeEmailTest::testWrapForJapaneseEncoding()
     *
     * @return void
     */
    public function testWrapForJapaneseEncoding()
    {
        $this->skipIf(!function_exists('mb_convert_encoding'));

        $message = mb_convert_encoding('受け付けました', 'iso-2022-jp', 'UTF-8');

        $this->message->setFrom('cake@cakephp.org');
        $this->message->setTo('cake@cakephp.org');
        $this->message->setSubject('Wordwrap Test');
        $this->message->setCharset('iso-2022-jp');
        $this->message->setHeaderCharset('iso-2022-jp');
        $this->message->setBodyText($message);

        $result = (new DebugTransport())->send($this->message);
        $expected = "{$message}\r\n\r\n";
        $this->assertSame($expected, $result['message']);
    }

    /**
     * testHeaders method
     *
     * @return void
     */
    public function testHeaders()
    {
        $this->message->setMessageId(false);
        $this->message->setHeaders(['X-Something' => 'nice']);
        $expected = [
            'X-Something' => 'nice',
            'Date' => date(DATE_RFC2822),
            'MIME-Version' => '1.0',
            'Content-Type' => 'text/plain; charset=UTF-8',
            'Content-Transfer-Encoding' => '8bit',
        ];
        $this->assertSame($expected, $this->message->getHeaders());

        $this->message->addHeaders(['X-Something' => 'very nice', 'X-Other' => 'cool']);
        $expected = [
            'X-Something' => 'very nice',
            'X-Other' => 'cool',
            'Date' => date(DATE_RFC2822),
            'MIME-Version' => '1.0',
            'Content-Type' => 'text/plain; charset=UTF-8',
            'Content-Transfer-Encoding' => '8bit',
        ];
        $this->assertSame($expected, $this->message->getHeaders());

        $this->message->setFrom('cake@cakephp.org');
        $this->assertSame($expected, $this->message->getHeaders());

        $expected = [
            'From' => 'cake@cakephp.org',
            'X-Something' => 'very nice',
            'X-Other' => 'cool',
            'Date' => date(DATE_RFC2822),
            'MIME-Version' => '1.0',
            'Content-Type' => 'text/plain; charset=UTF-8',
            'Content-Transfer-Encoding' => '8bit',
        ];
        $this->assertSame($expected, $this->message->getHeaders(['from' => true]));

        $this->message->setFrom('cake@cakephp.org', 'CakePHP');
        $expected['From'] = 'CakePHP <cake@cakephp.org>';
        $this->assertSame($expected, $this->message->getHeaders(['from' => true]));

        $this->message->setTo(['cake@cakephp.org', 'php@cakephp.org' => 'CakePHP']);
        $expected = [
            'From' => 'CakePHP <cake@cakephp.org>',
            'To' => 'cake@cakephp.org, CakePHP <php@cakephp.org>',
            'X-Something' => 'very nice',
            'X-Other' => 'cool',
            'Date' => date(DATE_RFC2822),
            'MIME-Version' => '1.0',
            'Content-Type' => 'text/plain; charset=UTF-8',
            'Content-Transfer-Encoding' => '8bit',
        ];
        $this->assertSame($expected, $this->message->getHeaders(['from' => true, 'to' => true]));

        $this->message->setCharset('ISO-2022-JP');
        $expected = [
            'From' => 'CakePHP <cake@cakephp.org>',
            'To' => 'cake@cakephp.org, CakePHP <php@cakephp.org>',
            'X-Something' => 'very nice',
            'X-Other' => 'cool',
            'Date' => date(DATE_RFC2822),
            'MIME-Version' => '1.0',
            'Content-Type' => 'text/plain; charset=ISO-2022-JP',
            'Content-Transfer-Encoding' => '7bit',
        ];
        $this->assertSame($expected, $this->message->getHeaders(['from' => true, 'to' => true]));

        $result = $this->message->setHeaders([]);
        $this->assertInstanceOf(Message::class, $result);

        $this->message->setHeaders(['o:tag' => ['foo']]);
        $this->message->addHeaders(['o:tag' => ['bar']]);
        $result = $this->message->getHeaders();
        $this->assertEquals(['foo', 'bar'], $result['o:tag']);
    }

    /**
     * testHeadersString method
     *
     * @return void
     */
    public function testHeadersString()
    {
        $this->message->setMessageId(false);
        $this->message->setHeaders(['X-Something' => 'nice']);
        $expected = [
            'X-Something: nice',
            'Date: ' . date(DATE_RFC2822),
            'MIME-Version: 1.0',
            'Content-Type: text/plain; charset=UTF-8',
            'Content-Transfer-Encoding: 8bit',
        ];
        $this->assertSame(implode("\r\n", $expected), $this->message->getHeadersString());
    }

    /**
     * testFrom method
     *
     * @return void
     */
    public function testFrom()
    {
        $this->assertSame([], $this->message->getFrom());

        $this->message->setFrom('cake@cakephp.org');
        $expected = ['cake@cakephp.org' => 'cake@cakephp.org'];
        $this->assertSame($expected, $this->message->getFrom());

        $this->message->setFrom(['cake@cakephp.org']);
        $this->assertSame($expected, $this->message->getFrom());

        $this->message->setFrom('cake@cakephp.org', 'CakePHP');
        $expected = ['cake@cakephp.org' => 'CakePHP'];
        $this->assertSame($expected, $this->message->getFrom());

        $result = $this->message->setFrom(['cake@cakephp.org' => 'CakePHP']);
        $this->assertSame($expected, $this->message->getFrom());
        $this->assertSame($this->message, $result);

        $this->expectException(\InvalidArgumentException::class);
        $result = $this->message->setFrom(['cake@cakephp.org' => 'CakePHP', 'fail@cakephp.org' => 'From can only be one address']);
    }

    /**
     * Test that from addresses using colons work.
     *
     * @return void
     */
    public function testFromWithColonsAndQuotes()
    {
        $address = [
            'info@example.com' => '70:20:00 " Forum',
        ];
        $this->message->setFrom($address);
        $this->assertEquals($address, $this->message->getFrom());

        $result = $this->message->getHeadersString(['from']);
        $this->assertStringContainsString('From: "70:20:00 \" Forum" <info@example.com>', $result);
    }

    /**
     * testSender method
     *
     * @return void
     */
    public function testSender()
    {
        $this->message->reset();
        $this->assertSame([], $this->message->getSender());

        $this->message->setSender('cake@cakephp.org', 'Name');
        $expected = ['cake@cakephp.org' => 'Name'];
        $this->assertSame($expected, $this->message->getSender());

        $headers = $this->message->getHeaders(['from' => true, 'sender' => true]);
        $this->assertSame('', $headers['From']);
        $this->assertSame('Name <cake@cakephp.org>', $headers['Sender']);

        $this->message->setFrom('cake@cakephp.org', 'CakePHP');
        $headers = $this->message->getHeaders(['from' => true, 'sender' => true]);
        $this->assertSame('CakePHP <cake@cakephp.org>', $headers['From']);
        $this->assertSame('', $headers['Sender']);
    }

    /**
     * testTo method
     *
     * @return void
     */
    public function testTo()
    {
        $this->assertSame([], $this->message->getTo());

        $result = $this->message->setTo('cake@cakephp.org');
        $expected = ['cake@cakephp.org' => 'cake@cakephp.org'];
        $this->assertSame($expected, $this->message->getTo());
        $this->assertSame($this->message, $result);

        $this->message->setTo('cake@cakephp.org', 'CakePHP');
        $expected = ['cake@cakephp.org' => 'CakePHP'];
        $this->assertSame($expected, $this->message->getTo());

        $list = [
            'root@localhost' => 'root',
            'bjørn@hammeröath.com' => 'Bjorn',
            'cake.php@cakephp.org' => 'Cake PHP',
            'cake-php@googlegroups.com' => 'Cake Groups',
            'root@cakephp.org',
        ];
        $this->message->setTo($list);
        $expected = [
            'root@localhost' => 'root',
            'bjørn@hammeröath.com' => 'Bjorn',
            'cake.php@cakephp.org' => 'Cake PHP',
            'cake-php@googlegroups.com' => 'Cake Groups',
            'root@cakephp.org' => 'root@cakephp.org',
        ];
        $this->assertSame($expected, $this->message->getTo());

        $this->message->addTo('jrbasso@cakephp.org');
        $this->message->addTo('mark_story@cakephp.org', 'Mark Story');
        $this->message->addTo('foobar@ætdcadsl.dk');
        $result = $this->message->addTo(['phpnut@cakephp.org' => 'PhpNut', 'jose_zap@cakephp.org']);
        $expected = [
            'root@localhost' => 'root',
            'bjørn@hammeröath.com' => 'Bjorn',
            'cake.php@cakephp.org' => 'Cake PHP',
            'cake-php@googlegroups.com' => 'Cake Groups',
            'root@cakephp.org' => 'root@cakephp.org',
            'jrbasso@cakephp.org' => 'jrbasso@cakephp.org',
            'mark_story@cakephp.org' => 'Mark Story',
            'foobar@ætdcadsl.dk' => 'foobar@ætdcadsl.dk',
            'phpnut@cakephp.org' => 'PhpNut',
            'jose_zap@cakephp.org' => 'jose_zap@cakephp.org',
        ];
        $this->assertSame($expected, $this->message->getTo());
        $this->assertSame($this->message, $result);
    }

    /**
     * test to address with _ in domain name
     *
     * @return void
     */
    public function testToUnderscoreDomain()
    {
        $result = $this->message->setTo('cake@cake_php.org');
        $expected = ['cake@cake_php.org' => 'cake@cake_php.org'];
        $this->assertSame($expected, $this->message->getTo());
        $this->assertSame($this->message, $result);
    }

    /**
     * Data provider function for testBuildInvalidData
     *
     * @return array
     */
    public static function invalidEmails()
    {
        return [
            [''],
            ['string'],
            ['<tag>'],
            [['ok@cakephp.org', '1.0', '', 'string']],
        ];
    }

    /**
     * testBuildInvalidData
     *
     * @dataProvider invalidEmails
     * @return void
     */
    public function testInvalidEmail($value)
    {
        $this->expectException(\InvalidArgumentException::class);
        $this->message->setTo($value);
    }

    /**
     * testBuildInvalidData
     *
     * @dataProvider invalidEmails
     * @return void
     */
    public function testInvalidEmailAdd($value)
    {
        $this->expectException(\InvalidArgumentException::class);
        $this->message->addTo($value);
    }

    /**
     * test emailPattern method
     *
     * @return void
     */
    public function testEmailPattern()
    {
        $regex = '/.+@.+\..+/i';
        $this->assertSame($regex, $this->message->setEmailPattern($regex)->getEmailPattern());
    }

    /**
     * Tests that it is possible to set email regex configuration to a CakeEmail object
     *
     * @return void
     */
    public function testConfigEmailPattern()
    {
        $regex = '/.+@.+\..+/i';
        $email = new Message(['emailPattern' => $regex]);
        $this->assertSame($regex, $email->getEmailPattern());
    }

    /**
     * Tests that it is possible set custom email validation
     *
     * @return void
     */
    public function testCustomEmailValidation()
    {
        $regex = '/^[\.a-z0-9!#$%&\'*+\/=?^_`{|}~-]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]{2,6}$/i';

        $this->message->setEmailPattern($regex)->setTo('pass.@example.com');
        $this->assertSame([
            'pass.@example.com' => 'pass.@example.com',
        ], $this->message->getTo());

        $this->message->addTo('pass..old.docomo@example.com');
        $this->assertSame([
            'pass.@example.com' => 'pass.@example.com',
            'pass..old.docomo@example.com' => 'pass..old.docomo@example.com',
        ], $this->message->getTo());

        $this->message->reset();
        $emails = [
            'pass.@example.com',
            'pass..old.docomo@example.com',
        ];
        $additionalEmails = [
            '.extend.@example.com',
            '.docomo@example.com',
        ];
        $this->message->setEmailPattern($regex)->setTo($emails);
        $this->assertSame([
            'pass.@example.com' => 'pass.@example.com',
            'pass..old.docomo@example.com' => 'pass..old.docomo@example.com',
        ], $this->message->getTo());

        $this->message->addTo($additionalEmails);
        $this->assertSame([
            'pass.@example.com' => 'pass.@example.com',
            'pass..old.docomo@example.com' => 'pass..old.docomo@example.com',
            '.extend.@example.com' => '.extend.@example.com',
            '.docomo@example.com' => '.docomo@example.com',
        ], $this->message->getTo());
    }

    /**
     * Tests that it is possible to unset the email pattern and make use of filter_var() instead.
     *
     * @return void
     */
    public function testUnsetEmailPattern()
    {
        $this->expectException(\InvalidArgumentException::class);
        $this->expectExceptionMessage('Invalid email set for "to". You passed "fail.@example.com".');
        $email = new Message();
        $this->assertSame(Message::EMAIL_PATTERN, $email->getEmailPattern());

        $email->setEmailPattern(null);
        $this->assertNull($email->getEmailPattern());

        $email->setTo('pass@example.com');
        $email->setTo('fail.@example.com');
    }

    /**
     * Tests that passing an empty string throws an InvalidArgumentException.
     *
     * @return void
     */
    public function testEmptyTo()
    {
        $this->expectException(\InvalidArgumentException::class);
        $this->expectExceptionMessage('The email set for "to" is empty.');
        $email = new Message();
        $email->setTo('');
    }

    /**
     * testFormatAddress method
     *
     * @return void
     */
    public function testFormatAddress()
    {
        $result = $this->message->fmtAddress(['cake@cakephp.org' => 'cake@cakephp.org']);
        $expected = ['cake@cakephp.org'];
        $this->assertSame($expected, $result);

        $result = $this->message->fmtAddress([
            'cake@cakephp.org' => 'cake@cakephp.org',
            'php@cakephp.org' => 'php@cakephp.org',
        ]);
        $expected = ['cake@cakephp.org', 'php@cakephp.org'];
        $this->assertSame($expected, $result);

        $result = $this->message->fmtAddress([
            'cake@cakephp.org' => 'CakePHP',
            'php@cakephp.org' => 'Cake',
        ]);
        $expected = ['CakePHP <cake@cakephp.org>', 'Cake <php@cakephp.org>'];
        $this->assertSame($expected, $result);

        $result = $this->message->fmtAddress(['me@example.com' => 'Last, First']);
        $expected = ['"Last, First" <me@example.com>'];
        $this->assertSame($expected, $result);

        $result = $this->message->fmtAddress(['me@example.com' => '"Last" First']);
        $expected = ['"\"Last\" First" <me@example.com>'];
        $this->assertSame($expected, $result);

        $result = $this->message->fmtAddress(['me@example.com' => 'Last First']);
        $expected = ['Last First <me@example.com>'];
        $this->assertSame($expected, $result);

        $result = $this->message->fmtAddress(['cake@cakephp.org' => 'ÄÖÜTest']);
        $expected = ['=?UTF-8?B?w4TDlsOcVGVzdA==?= <cake@cakephp.org>'];
        $this->assertSame($expected, $result);

        $result = $this->message->fmtAddress(['cake@cakephp.org' => '日本語Test']);
        $expected = ['=?UTF-8?B?5pel5pys6KqeVGVzdA==?= <cake@cakephp.org>'];
        $this->assertSame($expected, $result);
    }

    /**
     * testFormatAddressJapanese
     *
     * @return void
     */
    public function testFormatAddressJapanese()
    {
        $this->message->setHeaderCharset('ISO-2022-JP');
        $result = $this->message->fmtAddress(['cake@cakephp.org' => '日本語Test']);
        $expected = ['=?ISO-2022-JP?B?GyRCRnxLXDhsGyhCVGVzdA==?= <cake@cakephp.org>'];
        $this->assertSame($expected, $result);

        $result = $this->message->fmtAddress(['cake@cakephp.org' => '寿限無寿限無五劫の擦り切れ海砂利水魚の水行末雲来末風来末食う寝る処に住む処やぶら小路の藪柑子パイポパイポパイポのシューリンガンシューリンガンのグーリンダイグーリンダイのポンポコピーのポンポコナーの長久命の長助']);
        $expected = ["=?ISO-2022-JP?B?GyRCPHc4Qkw1PHc4Qkw1OF45ZSROOyQkakBaJGwzJDo9TXg/ZTV7GyhC?=\r\n" .
            " =?ISO-2022-JP?B?GyRCJE4/ZTlUS3YxQE1oS3ZJd01oS3Y/KSQmPzIkaz1oJEs9OyRgGyhC?=\r\n" .
            " =?ISO-2022-JP?B?GyRCPWgkZCRWJGk+Lk8pJE5pLjQ7O1IlUSUkJV0lUSUkJV0lUSUkGyhC?=\r\n" .
            " =?ISO-2022-JP?B?GyRCJV0kTiU3JWUhPCVqJXMlLCVzJTclZSE8JWolcyUsJXMkTiUwGyhC?=\r\n" .
            " =?ISO-2022-JP?B?GyRCITwlaiVzJUAlJCUwITwlaiVzJUAlJCROJV0lcyVdJTMlVCE8GyhC?=\r\n" .
            ' =?ISO-2022-JP?B?GyRCJE4lXSVzJV0lMyVKITwkTkQ5NVdMPyRORDk9dRsoQg==?= <cake@cakephp.org>'];
        $this->assertSame($expected, $result);
    }

    /**
     * testAddresses method
     *
     * @return void
     */
    public function testAddresses()
    {
        $this->message->reset();
        $this->message->setFrom('cake@cakephp.org', 'CakePHP');
        $this->message->setReplyTo('replyto@cakephp.org', 'ReplyTo CakePHP');
        $this->message->setReadReceipt('readreceipt@cakephp.org', 'ReadReceipt CakePHP');
        $this->message->setReturnPath('returnpath@cakephp.org', 'ReturnPath CakePHP');
        $this->message->setTo('to@cakephp.org', 'To, CakePHP');
        $this->message->setCc('cc@cakephp.org', 'Cc CakePHP');
        $this->message->setBcc('bcc@cakephp.org', 'Bcc CakePHP');
        $this->message->addTo('to2@cakephp.org', 'To2 CakePHP');
        $this->message->addCc('cc2@cakephp.org', 'Cc2 CakePHP');
        $this->message->addBcc('bcc2@cakephp.org', 'Bcc2 CakePHP');
        $this->message->addReplyTo('replyto2@cakephp.org', 'ReplyTo2 CakePHP');

        $this->assertSame($this->message->getFrom(), ['cake@cakephp.org' => 'CakePHP']);
        $this->assertSame($this->message->getReplyTo(), ['replyto@cakephp.org' => 'ReplyTo CakePHP', 'replyto2@cakephp.org' => 'ReplyTo2 CakePHP']);
        $this->assertSame($this->message->getReadReceipt(), ['readreceipt@cakephp.org' => 'ReadReceipt CakePHP']);
        $this->assertSame($this->message->getReturnPath(), ['returnpath@cakephp.org' => 'ReturnPath CakePHP']);
        $this->assertSame($this->message->getTo(), ['to@cakephp.org' => 'To, CakePHP', 'to2@cakephp.org' => 'To2 CakePHP']);
        $this->assertSame($this->message->getCc(), ['cc@cakephp.org' => 'Cc CakePHP', 'cc2@cakephp.org' => 'Cc2 CakePHP']);
        $this->assertSame($this->message->getBcc(), ['bcc@cakephp.org' => 'Bcc CakePHP', 'bcc2@cakephp.org' => 'Bcc2 CakePHP']);

        $headers = $this->message->getHeaders(array_fill_keys(['from', 'replyTo', 'readReceipt', 'returnPath', 'to', 'cc', 'bcc'], true));
        $this->assertSame($headers['From'], 'CakePHP <cake@cakephp.org>');
        $this->assertSame($headers['Reply-To'], 'ReplyTo CakePHP <replyto@cakephp.org>, ReplyTo2 CakePHP <replyto2@cakephp.org>');
        $this->assertSame($headers['Disposition-Notification-To'], 'ReadReceipt CakePHP <readreceipt@cakephp.org>');
        $this->assertSame($headers['Return-Path'], 'ReturnPath CakePHP <returnpath@cakephp.org>');
        $this->assertSame($headers['To'], '"To, CakePHP" <to@cakephp.org>, To2 CakePHP <to2@cakephp.org>');
        $this->assertSame($headers['Cc'], 'Cc CakePHP <cc@cakephp.org>, Cc2 CakePHP <cc2@cakephp.org>');
        $this->assertSame($headers['Bcc'], 'Bcc CakePHP <bcc@cakephp.org>, Bcc2 CakePHP <bcc2@cakephp.org>');

        $this->message->setReplyTo(['replyto@cakephp.org' => 'ReplyTo CakePHP', 'replyto2@cakephp.org' => 'ReplyTo2 CakePHP']);
        $this->assertSame($this->message->getReplyTo(), ['replyto@cakephp.org' => 'ReplyTo CakePHP', 'replyto2@cakephp.org' => 'ReplyTo2 CakePHP']);
        $headers = $this->message->getHeaders(array_fill_keys(['replyTo'], true));
        $this->assertSame($headers['Reply-To'], 'ReplyTo CakePHP <replyto@cakephp.org>, ReplyTo2 CakePHP <replyto2@cakephp.org>');
    }

    /**
     * test reset addresses method
     *
     * @return void
     */
    public function testResetAddresses()
    {
        $this->message->reset();
        $this->message
            ->setFrom('cake@cakephp.org', 'CakePHP')
            ->setReplyTo('replyto@cakephp.org', 'ReplyTo CakePHP')
            ->setReadReceipt('readreceipt@cakephp.org', 'ReadReceipt CakePHP')
            ->setReturnPath('returnpath@cakephp.org', 'ReturnPath CakePHP')
            ->setTo('to@cakephp.org', 'To, CakePHP')
            ->setCc('cc@cakephp.org', 'Cc CakePHP')
            ->setBcc('bcc@cakephp.org', 'Bcc CakePHP');

        $this->assertNotEmpty($this->message->getFrom());
        $this->assertNotEmpty($this->message->getReplyTo());
        $this->assertNotEmpty($this->message->getReadReceipt());
        $this->assertNotEmpty($this->message->getReturnPath());
        $this->assertNotEmpty($this->message->getTo());
        $this->assertNotEmpty($this->message->getCc());
        $this->assertNotEmpty($this->message->getBcc());

        $this->message
            ->setFrom([])
            ->setReplyTo([])
            ->setReadReceipt([])
            ->setReturnPath([])
            ->setTo([])
            ->setCc([])
            ->setBcc([]);

        $this->assertEmpty($this->message->getFrom());
        $this->assertEmpty($this->message->getReplyTo());
        $this->assertEmpty($this->message->getReadReceipt());
        $this->assertEmpty($this->message->getReturnPath());
        $this->assertEmpty($this->message->getTo());
        $this->assertEmpty($this->message->getCc());
        $this->assertEmpty($this->message->getBcc());
    }

    /**
     * testMessageId method
     *
     * @return void
     */
    public function testMessageId()
    {
        $this->message->setMessageId(true);
        $result = $this->message->getHeaders();
        $this->assertArrayHasKey('Message-ID', $result);

        $this->message->setMessageId(false);
        $result = $this->message->getHeaders();
        $this->assertArrayNotHasKey('Message-ID', $result);

        $result = $this->message->setMessageId('<my-email@localhost>');
        $this->assertSame($this->message, $result);
        $result = $this->message->getHeaders();
        $this->assertSame('<my-email@localhost>', $result['Message-ID']);

        $result = $this->message->getMessageId();
        $this->assertSame('<my-email@localhost>', $result);
    }

    public function testAutoMessageIdIsIdempotent()
    {
        $headers = $this->message->getHeaders();
        $this->assertArrayHasKey('Message-ID', $headers);

        $regeneratedHeaders = $this->message->getHeaders();
        $this->assertSame($headers['Message-ID'], $regeneratedHeaders['Message-ID']);
    }

    /**
     * @return void
     */
    public function testPriority()
    {
        $this->message->setPriority(4);

        $this->assertSame(4, $this->message->getPriority());

        $result = $this->message->getHeaders();
        $this->assertArrayHasKey('X-Priority', $result);
    }

    /**
     * testMessageIdInvalid method
     *
     * @return void
     */
    public function testMessageIdInvalid()
    {
        $this->expectException(\InvalidArgumentException::class);
        $this->message->setMessageId('my-email@localhost');
    }

    /**
     * testDomain method
     *
     * @return void
     */
    public function testDomain()
    {
        $result = $this->message->getDomain();
        $expected = env('HTTP_HOST') ? env('HTTP_HOST') : php_uname('n');
        $this->assertSame($expected, $result);

        $this->message->setDomain('example.org');
        $result = $this->message->getDomain();
        $expected = 'example.org';
        $this->assertSame($expected, $result);
    }

    /**
     * testMessageIdWithDomain method
     *
     * @return void
     */
    public function testMessageIdWithDomain()
    {
        $this->message->setDomain('example.org');
        $result = $this->message->getHeaders();
        $expected = '@example.org>';
        $this->assertTextContains($expected, $result['Message-ID']);

        $_SERVER['HTTP_HOST'] = 'example.org';
        $result = $this->message->getHeaders();
        $this->assertTextContains('example.org', $result['Message-ID']);

        $_SERVER['HTTP_HOST'] = 'example.org:81';
        $result = $this->message->getHeaders();
        $this->assertTextNotContains(':81', $result['Message-ID']);
    }

    /**
     * testSubject method
     *
     * @return void
     */
    public function testSubject()
    {
        $this->message->setSubject('You have a new message.');
        $this->assertSame('You have a new message.', $this->message->getSubject());

        $this->message->setSubject('You have a new message, I think.');
        $this->assertSame($this->message->getSubject(), 'You have a new message, I think.');
        $this->message->setSubject('1');
        $this->assertSame('1', $this->message->getSubject());

        $input = 'هذه رسالة بعنوان طويل مرسل للمستلم';
        $this->message->setSubject($input);
        $expected = '=?UTF-8?B?2YfYsNmHINix2LPYp9mE2Kkg2KjYudmG2YjYp9mGINi32YjZitmEINmF2LE=?=' . "\r\n" . ' =?UTF-8?B?2LPZhCDZhNmE2YXYs9iq2YTZhQ==?=';
        $this->assertSame($expected, $this->message->getSubject());
        $this->assertSame($input, $this->message->getOriginalSubject());
    }

    /**
     * testSubjectJapanese
     *
     * @return void
     */
    public function testSubjectJapanese()
    {
        mb_internal_encoding('UTF-8');

        $this->message->setHeaderCharset('ISO-2022-JP');
        $this->message->setSubject('日本語のSubjectにも対応するよ');
        $expected = '=?ISO-2022-JP?B?GyRCRnxLXDhsJE4bKEJTdWJqZWN0GyRCJEskYkJQMX4kOSRrJGgbKEI=?=';
        $this->assertSame($expected, $this->message->getSubject());

        $this->message->setSubject('長い長い長いSubjectの場合はfoldingするのが正しいんだけどいったいどうなるんだろう？');
        $expected = "=?ISO-2022-JP?B?GyRCRDkkJEQ5JCREOSQkGyhCU3ViamVjdBskQiROPmw5ZyRPGyhCZm9s?=\r\n" .
            " =?ISO-2022-JP?B?ZGluZxskQiQ5JGskTiQsQDUkNyQkJHMkQCQxJEkkJCRDJD8kJCRJGyhC?=\r\n" .
            ' =?ISO-2022-JP?B?GyRCJCYkSiRrJHMkQCRtJCYhKRsoQg==?=';
        $this->assertSame($expected, $this->message->getSubject());
    }

    /**
     * testAttachments method
     *
     * @return void
     */
    public function testSetAttachments()
    {
        $uploadedFile = new UploadedFile(
            __FILE__,
            filesize(__FILE__),
            UPLOAD_ERR_OK,
            'MessageTest.php',
            'text/x-php'
        );

        $this->message->setAttachments([
            CAKE . 'basics.php',
            $uploadedFile,
        ]);
        $expected = [
            'basics.php' => [
                'file' => CAKE . 'basics.php',
                'mimetype' => 'text/x-php',
            ],
            'MessageTest.php' => [
                'file' => $uploadedFile,
                'mimetype' => 'text/x-php',
            ],
        ];
        $this->assertSame($expected, $this->message->getAttachments());

        $this->message->setAttachments([]);
        $this->assertSame([], $this->message->getAttachments());

        $this->message->setAttachments([
            ['file' => CAKE . 'basics.php', 'mimetype' => 'text/plain'],
        ]);
        $this->message->addAttachments([CORE_PATH . 'config' . DS . 'bootstrap.php']);
        $this->message->addAttachments([CORE_PATH . 'config' . DS . 'bootstrap.php']);
        $this->message->addAttachments([
            'other.txt' => CORE_PATH . 'config' . DS . 'bootstrap.php',
            'license' => CORE_PATH . 'LICENSE',
        ]);
        $expected = [
            'basics.php' => ['file' => CAKE . 'basics.php', 'mimetype' => 'text/plain'],
            'bootstrap.php' => ['file' => CORE_PATH . 'config' . DS . 'bootstrap.php', 'mimetype' => 'text/x-php'],
            'other.txt' => ['file' => CORE_PATH . 'config' . DS . 'bootstrap.php', 'mimetype' => 'text/x-php'],
            'license' => ['file' => CORE_PATH . 'LICENSE', 'mimetype' => 'text/plain'],
        ];
        $this->assertSame($expected, $this->message->getAttachments());
        $this->expectException(\InvalidArgumentException::class);
        $this->message->setAttachments([['nofile' => CAKE . 'basics.php', 'mimetype' => 'text/plain']]);
    }

    /**
     * Test send() with no template and data string attachment and no mimetype
     *
     * @return void
     */
    public function testSetAttachmentDataNoMimetype()
    {
        $this->message->setAttachments(['cake.icon.gif' => [
            'data' => 'test',
        ]]);
        $result = $this->message->getAttachments();
        $expected = [
            'cake.icon.gif' => [
                'data' => base64_encode('test') . "\r\n",
                'mimetype' => 'application/octet-stream',
            ],
        ];
        $this->assertSame($expected, $this->message->getAttachments());
    }

    public function testSetAttachmentInvalidFile()
    {
        $this->expectException(\InvalidArgumentException::class);
        $this->expectExceptionMessage(
            'File must be a filepath or UploadedFileInterface instance. Found `boolean` instead.'
        );

        $this->message->setAttachments(['cake.icon.gif' => [
            'file' => true,
        ]]);
    }

    /**
     * testReset method
     *
     * @return void
     */
    public function testReset()
    {
        $this->message->setTo('cake@cakephp.org');
        $this->message->setEmailPattern('/.+@.+\..+/i');
        $this->assertSame(['cake@cakephp.org' => 'cake@cakephp.org'], $this->message->getTo());

        $this->message->reset();
        $this->assertSame([], $this->message->getTo());
        $this->assertSame(Message::EMAIL_PATTERN, $this->message->getEmailPattern());
    }

    /**
     * testReset with charset
     *
     * @return void
     */
    public function testResetWithCharset()
    {
        $this->message->setCharset('ISO-2022-JP');
        $this->message->reset();

        $this->assertSame('utf-8', $this->message->getCharset());
        $this->assertSame('utf-8', $this->message->getHeaderCharset());
    }

    /**
     * testEmailFormat method
     *
     * @return void
     */
    public function testEmailFormat()
    {
        $result = $this->message->getEmailFormat();
        $this->assertSame('text', $result);

        $result = $this->message->setEmailFormat('html');
        $this->assertInstanceOf(Message::class, $result);

        $result = $this->message->getEmailFormat();
        $this->assertSame('html', $result);

        $this->expectException(\InvalidArgumentException::class);
        $this->message->setEmailFormat('invalid');
    }

    /**
     * Tests that it is possible to add charset configuration to a CakeEmail object
     *
     * @return void
     */
    public function testConfigCharset()
    {
        $email = new Message();
        $this->assertEquals(Configure::read('App.encoding'), $email->getCharset());
        $this->assertEquals(Configure::read('App.encoding'), $email->getHeaderCharset());

        $email = new Message(['charset' => 'iso-2022-jp', 'headerCharset' => 'iso-2022-jp-ms']);
        $this->assertSame('iso-2022-jp', $email->getCharset());
        $this->assertSame('iso-2022-jp-ms', $email->getHeaderCharset());

        $email = new Message(['charset' => 'iso-2022-jp']);
        $this->assertSame('iso-2022-jp', $email->getCharset());
        $this->assertSame('iso-2022-jp', $email->getHeaderCharset());

        $email = new Message(['headerCharset' => 'iso-2022-jp-ms']);
        $this->assertEquals(Configure::read('App.encoding'), $email->getCharset());
        $this->assertSame('iso-2022-jp-ms', $email->getHeaderCharset());
    }

    public function testGetBody()
    {
        $message = new Message();

        $uploadedFile = new UploadedFile(
            __FILE__,
            filesize(__FILE__),
            UPLOAD_ERR_OK,
            'MessageTest.php',
            'text/x-php'
        );
        $chunks = base64_encode(file_get_contents(__FILE__));

        $result = $message->setAttachments([$uploadedFile])
            ->setBodyText('Attached an uploaded file')
            ->getBody();
        $result = implode("\r\n", $result);
        $this->assertStringContainsString($chunks[0], $result);
    }

    /**
     * Tests that the body is encoded using the configured charset (Japanese standard encoding)
     *
     * @return void
     */
    public function testBodyEncodingIso2022Jp()
    {
        $message = new Message([
            'charset' => 'iso-2022-jp',
            'headerCharset' => 'iso-2022-jp',
            'transport' => 'debug',
        ]);
        $message->setSubject('あれ？もしかしての前と');

        $headers = $message->getHeaders(['subject']);
        $expected = '?ISO-2022-JP?B?GyRCJCIkbCEpJGIkNyQrJDckRiROQTAkSBsoQg==?=';
        $this->assertStringContainsString($expected, $headers['Subject']);

        $message->setBodyHtml('①㈱ ってテーブルを作ってやってたらう');

        $result = $message->getHeadersString();
        $this->assertTextContains('Content-Type: text/plain; charset=ISO-2022-JP', $result);
        $this->assertTextNotContains('Content-Type: text/plain; charset=ISO-2022-JP-MS', $result); // not charset=iso-2022-jp-ms

        $result = implode('', $message->getBody());
        $this->assertTextNotContains(mb_convert_encoding('①㈱ ってテーブルを作ってやってたらう', 'ISO-2022-JP-MS'), $result);
    }

    /**
     * Tests that the body is encoded using the configured charset (Japanese irregular encoding, but sometime use this)
     *
     * @return void
     */
    public function testBodyEncodingIso2022JpMs()
    {
        $message = new Message([
            'charset' => 'iso-2022-jp-ms',
            'headerCharset' => 'iso-2022-jp-ms',
            'transport' => 'debug',
        ]);
        $message->setSubject('あれ？もしかしての前と');
        $headers = $message->getHeaders(['subject']);
        $expected = '?ISO-2022-JP?B?GyRCJCIkbCEpJGIkNyQrJDckRiROQTAkSBsoQg==?=';
        $this->assertStringContainsString($expected, $headers['Subject']);

        $result = $message->setBodyText('①㈱ ってテーブルを作ってやってたらう');

        $result = $message->getHeadersString();
        $this->assertTextContains('Content-Type: text/plain; charset=ISO-2022-JP', $result);
        $this->assertTextNotContains('Content-Type: text/plain; charset=iso-2022-jp-ms', $result); // not charset=iso-2022-jp-ms

        $result = implode('', $message->getBody());
        $this->assertStringContainsString(mb_convert_encoding('①㈱ ってテーブルを作ってやってたらう', 'ISO-2022-JP-MS'), $result);
    }

    /**
     * Tests that the body is encoded using the configured charset
     *
     * @return void
     */
    public function testEncodingMixed()
    {
        $message = new Message([
            'headerCharset' => 'iso-2022-jp-ms',
            'charset' => 'iso-2022-jp',
        ]);

        $message->setBodyText('ってテーブルを作ってやってたらう');

        $result = $message->getHeadersString();
        $this->assertStringContainsString('Content-Type: text/plain; charset=ISO-2022-JP', $result);

        $result = implode('', $message->getBody());
        $this->assertStringContainsString(mb_convert_encoding('ってテーブルを作ってやってたらう', 'ISO-2022-JP'), $result);
    }

    /**
     * Test CakeMessage::_encode function
     *
     * @return void
     */
    public function testEncode()
    {
        $this->message->setHeaderCharset('ISO-2022-JP');
        $result = $this->message->encode('日本語');
        $expected = '=?ISO-2022-JP?B?GyRCRnxLXDhsGyhC?=';
        $this->assertSame($expected, $result);

        $this->message->setHeaderCharset('ISO-2022-JP');
        $result = $this->message->encode('長い長い長いSubjectの場合はfoldingするのが正しいんだけどいったいどうなるんだろう？');
        $expected = "=?ISO-2022-JP?B?GyRCRDkkJEQ5JCREOSQkGyhCU3ViamVjdBskQiROPmw5ZyRPGyhCZm9s?=\r\n" .
            " =?ISO-2022-JP?B?ZGluZxskQiQ5JGskTiQsQDUkNyQkJHMkQCQxJEkkJCRDJD8kJCRJGyhC?=\r\n" .
            ' =?ISO-2022-JP?B?GyRCJCYkSiRrJHMkQCRtJCYhKRsoQg==?=';
        $this->assertSame($expected, $result);
    }

    /**
     * Test CakeMessage::_decode function
     *
     * @return void
     */
    public function testDecode()
    {
        $this->message->setHeaderCharset('ISO-2022-JP');
        $result = $this->message->decode('=?ISO-2022-JP?B?GyRCRnxLXDhsGyhC?=');
        $expected = '日本語';
        $this->assertSame($expected, $result);

        $this->message->setHeaderCharset('ISO-2022-JP');
        $result = $this->message->decode("=?ISO-2022-JP?B?GyRCRDkkJEQ5JCREOSQkGyhCU3ViamVjdBskQiROPmw5ZyRPGyhCZm9s?=\r\n" .
            " =?ISO-2022-JP?B?ZGluZxskQiQ5JGskTiQsQDUkNyQkJHMkQCQxJEkkJCRDJD8kJCRJGyhC?=\r\n" .
            ' =?ISO-2022-JP?B?GyRCJCYkSiRrJHMkQCRtJCYhKRsoQg==?=');
        $expected = '長い長い長いSubjectの場合はfoldingするのが正しいんだけどいったいどうなるんだろう？';
        $this->assertSame($expected, $result);
    }

    /**
     * Tests charset setter/getter
     *
     * @return void
     */
    public function testCharset()
    {
        $this->message->setCharset('UTF-8');
        $this->assertSame($this->message->getCharset(), 'UTF-8');

        $this->message->setCharset('ISO-2022-JP');
        $this->assertSame($this->message->getCharset(), 'ISO-2022-JP');

        $charset = $this->message->setCharset('Shift_JIS');
        $this->assertSame('Shift_JIS', $charset->getCharset());
    }

    /**
     * Tests headerCharset setter/getter
     *
     * @return void
     */
    public function testHeaderCharset()
    {
        $this->message->setHeaderCharset('UTF-8');
        $this->assertSame($this->message->getHeaderCharset(), 'UTF-8');

        $this->message->setHeaderCharset('ISO-2022-JP');
        $this->assertSame($this->message->getHeaderCharset(), 'ISO-2022-JP');

        $charset = $this->message->setHeaderCharset('Shift_JIS');
        $this->assertSame('Shift_JIS', $charset->getHeaderCharset());
    }

    /**
     * Test transferEncoding
     *
     * @return void
     */
    public function testTransferEncoding()
    {
        // Test new transfer encoding
        $expected = 'quoted-printable';
        $this->message->setTransferEncoding($expected);
        $this->assertSame($expected, $this->message->getTransferEncoding());
        $this->assertSame($expected, $this->message->getContentTransferEncoding());

        // Test default charset/encoding : utf8/8bit
        $expected = '8bit';
        $this->message->reset();
        $this->assertNull($this->message->getTransferEncoding());
        $this->assertSame($expected, $this->message->getContentTransferEncoding());

        //Test wrong encoding
        $this->expectException(\InvalidArgumentException::class);
        $this->message->setTransferEncoding('invalid');
    }

    /**
     * Tests for compatible check.
     *          charset property and       charset() method.
     *    headerCharset property and headerCharset() method.
     *
     * @return void
     */
    public function testCharsetsCompatible()
    {
        $checkHeaders = [
            'from' => true,
            'to' => true,
            'cc' => true,
            'subject' => true,
        ];

        // Header Charset : null (used by default UTF-8)
        //   Body Charset : ISO-2022-JP
        $oldStyleEmail = $this->_getEmailByOldStyleCharset('iso-2022-jp', null);
        $oldStyleHeaders = $oldStyleEmail->getHeaders($checkHeaders);

        $newStyleEmail = $this->_getEmailByNewStyleCharset('iso-2022-jp', null);
        $newStyleHeaders = $newStyleEmail->getHeaders($checkHeaders);

        $this->assertSame($oldStyleHeaders['From'], $newStyleHeaders['From']);
        $this->assertSame($oldStyleHeaders['To'], $newStyleHeaders['To']);
        $this->assertSame($oldStyleHeaders['Cc'], $newStyleHeaders['Cc']);
        $this->assertSame($oldStyleHeaders['Subject'], $newStyleHeaders['Subject']);

        // Header Charset : UTF-8
        //   Boby Charset : ISO-2022-JP
        $oldStyleEmail = $this->_getEmailByOldStyleCharset('iso-2022-jp', 'utf-8');
        $oldStyleHeaders = $oldStyleEmail->getHeaders($checkHeaders);

        $newStyleEmail = $this->_getEmailByNewStyleCharset('iso-2022-jp', 'utf-8');
        $newStyleHeaders = $newStyleEmail->getHeaders($checkHeaders);

        $this->assertSame($oldStyleHeaders['From'], $newStyleHeaders['From']);
        $this->assertSame($oldStyleHeaders['To'], $newStyleHeaders['To']);
        $this->assertSame($oldStyleHeaders['Cc'], $newStyleHeaders['Cc']);
        $this->assertSame($oldStyleHeaders['Subject'], $newStyleHeaders['Subject']);

        // Header Charset : ISO-2022-JP
        //   Boby Charset : UTF-8
        $oldStyleEmail = $this->_getEmailByOldStyleCharset('utf-8', 'iso-2022-jp');
        $oldStyleHeaders = $oldStyleEmail->getHeaders($checkHeaders);

        $newStyleEmail = $this->_getEmailByNewStyleCharset('utf-8', 'iso-2022-jp');
        $newStyleHeaders = $newStyleEmail->getHeaders($checkHeaders);

        $this->assertSame($oldStyleHeaders['From'], $newStyleHeaders['From']);
        $this->assertSame($oldStyleHeaders['To'], $newStyleHeaders['To']);
        $this->assertSame($oldStyleHeaders['Cc'], $newStyleHeaders['Cc']);
        $this->assertSame($oldStyleHeaders['Subject'], $newStyleHeaders['Subject']);
    }

    /**
     * @param mixed $charset
     * @param mixed $headerCharset
     * @return \Cake\Mailer\Message
     */
    protected function _getEmailByOldStyleCharset($charset, $headerCharset)
    {
        $message = new Message(['transport' => 'debug']);

        if (!empty($charset)) {
            $message->setCharset($charset);
        }
        if (!empty($headerCharset)) {
            $message->setHeaderCharset($headerCharset);
        }

        $message->setFrom('someone@example.com', 'どこかの誰か');
        $message->setTo('someperson@example.jp', 'どこかのどなたか');
        $message->setCc('miku@example.net', 'ミク');
        $message->setSubject('テストメール');
        $message->setBodyText('テストメールの本文');

        return $message;
    }

    /**
     * @param mixed $charset
     * @param mixed $headerCharset
     * @return \Cake\Mailer\Message
     */
    protected function _getEmailByNewStyleCharset($charset, $headerCharset)
    {
        $message = new Message();

        if (!empty($charset)) {
            $message->setCharset($charset);
        }
        if (!empty($headerCharset)) {
            $message->setHeaderCharset($headerCharset);
        }

        $message->setFrom('someone@example.com', 'どこかの誰か');
        $message->setTo('someperson@example.jp', 'どこかのどなたか');
        $message->setCc('miku@example.net', 'ミク');
        $message->setSubject('テストメール');
        $message->setBodyText('テストメールの本文');

        return $message;
    }

    /**
     * @param string $message
     * @return void
     */
    protected function assertLineLengths($message)
    {
        $lines = explode("\r\n", $message);
        foreach ($lines as $line) {
            $this->assertTrue(
                strlen($line) <= Message::LINE_LENGTH_MUST,
                'Line length exceeds the max. limit of Message::LINE_LENGTH_MUST'
            );
        }
    }

    public function testSerialization()
    {
        $message = new Message();

        $message
            ->setSubject('I haz Cake')
            ->setEmailFormat(Message::MESSAGE_BOTH)
            ->setBody([
                Message::MESSAGE_TEXT => 'text message',
                Message::MESSAGE_HTML => '<strong>html message</strong>',
            ]);

        $string = serialize($message);
        $this->assertStringContainsString('text message', $string);

        /** @var \Cake\Mailer\Message $message */
        $message = unserialize($string);
        $this->assertSame('I haz Cake', $message->getSubject());
        $body = $message->getBodyString();
        $this->assertStringContainsString('text message', $body);
        $this->assertStringContainsString('<strong>html message</strong>', $body);
    }
}
