use Mojo::Base -strict;

use Test::More;
use Mojo::Headers;

subtest 'Basic functionality' => sub {
  my $headers = Mojo::Headers->new;
  $headers->add(Connection => 'close');
  $headers->add(Connection => 'keep-alive');
  is_deeply $headers->every_header('Connection'), ['close', 'keep-alive'], 'right structure';
  is $headers->header('Connection'), 'close, keep-alive', 'right value';
  $headers->remove('Connection');
  is $headers->header('Connection'), undef, 'no value';
  is $headers->connection,           undef, 'no value';
  $headers->content_type('text/text')->content_type('text/html');
  $headers->expect('continue-100');
  $headers->connection('close');
  is $headers->content_type, 'text/html', 'right value';
  like $headers->to_string, qr/.*\x0d\x0a.*\x0d\x0a.*/, 'right format';
  my $hash = $headers->to_hash;
  is $hash->{Connection},     'close',        'right value';
  is $hash->{Expect},         'continue-100', 'right value';
  is $hash->{'Content-Type'}, 'text/html',    'right value';
  $hash = $headers->to_hash(1);
  is_deeply $hash->{Connection},       ['close'],                            'right structure';
  is_deeply $hash->{Expect},           ['continue-100'],                     'right structure';
  is_deeply $hash->{'Content-Type'},   ['text/html'],                        'right structure';
  is_deeply [sort @{$headers->names}], [qw(Connection Content-Type Expect)], 'right structure';
  $headers->expires('Thu, 01 Dec 1994 16:00:00 GMT');
  $headers->cache_control('public');
  is $headers->expires,       'Thu, 01 Dec 1994 16:00:00 GMT', 'right value';
  is $headers->cache_control, 'public',                        'right value';
  $headers->etag('abc321');
  is $headers->etag,           'abc321',       'right value';
  is $headers->header('ETag'), $headers->etag, 'values are equal';
  $headers->status('200 OK');
  is $headers->status,           '200 OK',         'right value';
  is $headers->header('Status'), $headers->status, 'values are equal';
};

subtest 'Mixed case' => sub {
  my $headers = Mojo::Headers->new;
  is $headers->header('content-type' => 'text/plain')->content_type, 'text/plain', 'right value';
  is $headers->header('Content-Type' => 'text/html')->content_type,  'text/html',  'right value';
};

subtest 'Common headers' => sub {
  my $headers = Mojo::Headers->new;
  is $headers->accept('foo')->accept,                                           'foo', 'right value';
  is $headers->accept_charset('foo')->accept_charset,                           'foo', 'right value';
  is $headers->accept_encoding('foo')->accept_encoding,                         'foo', 'right value';
  is $headers->accept_language('foo')->accept_language,                         'foo', 'right value';
  is $headers->accept_ranges('foo')->accept_ranges,                             'foo', 'right value';
  is $headers->access_control_allow_origin('foo')->access_control_allow_origin, 'foo', 'right value';
  is $headers->allow('foo')->allow,                                             'foo', 'right value';
  is $headers->authorization('foo')->authorization,                             'foo', 'right value';
  is $headers->connection('foo')->connection,                                   'foo', 'right value';
  is $headers->cache_control('foo')->cache_control,                             'foo', 'right value';
  is $headers->content_disposition('foo')->content_disposition,                 'foo', 'right value';
  is $headers->content_encoding('foo')->content_encoding,                       'foo', 'right value';
  is $headers->content_language('foo')->content_language,                       'foo', 'right value';
  is $headers->content_length('foo')->content_length,                           'foo', 'right value';
  is $headers->content_location('foo')->content_location,                       'foo', 'right value';
  is $headers->content_range('foo')->content_range,                             'foo', 'right value';
  is $headers->content_security_policy('foo')->content_security_policy,         'foo', 'right value';
  is $headers->content_type('foo')->content_type,                               'foo', 'right value';
  is $headers->cookie('foo')->cookie,                                           'foo', 'right value';
  is $headers->dnt('foo')->dnt,                                                 'foo', 'right value';
  is $headers->date('foo')->date,                                               'foo', 'right value';
  is $headers->etag('foo')->etag,                                               'foo', 'right value';
  is $headers->expect('foo')->expect,                                           'foo', 'right value';
  is $headers->expires('foo')->expires,                                         'foo', 'right value';
  is $headers->host('foo')->host,                                               'foo', 'right value';
  is $headers->if_modified_since('foo')->if_modified_since,                     'foo', 'right value';
  is $headers->last_modified('foo')->last_modified,                             'foo', 'right value';
  is $headers->link('foo')->link,                                               'foo', 'right value';
  is $headers->location('foo')->location,                                       'foo', 'right value';
  is $headers->origin('foo')->origin,                                           'foo', 'right value';
  is $headers->proxy_authenticate('foo')->proxy_authenticate,                   'foo', 'right value';
  is $headers->proxy_authorization('foo')->proxy_authorization,                 'foo', 'right value';
  is $headers->range('foo')->range,                                             'foo', 'right value';
  is $headers->sec_websocket_accept('foo')->sec_websocket_accept,               'foo', 'right value';
  is $headers->sec_websocket_extensions('foo')->sec_websocket_extensions,       'foo', 'right value';
  is $headers->sec_websocket_key('foo')->sec_websocket_key,                     'foo', 'right value';
  is $headers->sec_websocket_protocol('foo')->sec_websocket_protocol,           'foo', 'right value';
  is $headers->sec_websocket_version('foo')->sec_websocket_version,             'foo', 'right value';
  is $headers->server('foo')->server,                                           'foo', 'right value';
  is $headers->server_timing('foo')->server_timing,                             'foo', 'right value';
  is $headers->set_cookie('foo')->set_cookie,                                   'foo', 'right value';
  is $headers->status('foo')->status,                                           'foo', 'right value';
  is $headers->strict_transport_security('foo')->strict_transport_security,     'foo', 'right value';
  is $headers->te('foo')->te,                                                   'foo', 'right value';
  is $headers->trailer('foo')->trailer,                                         'foo', 'right value';
  is $headers->transfer_encoding('foo')->transfer_encoding,                     'foo', 'right value';
  is $headers->upgrade('foo')->upgrade,                                         'foo', 'right value';
  is $headers->user_agent('foo')->user_agent,                                   'foo', 'right value';
  is $headers->vary('foo')->vary,                                               'foo', 'right value';
  is $headers->www_authenticate('foo')->www_authenticate,                       'foo', 'right value';

  is $headers->referrer('foo')->referrer, 'foo', 'right value';
  is $headers->referer,                   'foo', 'right value';
  is $headers->referer('bar')->referer,   'bar', 'right value';
  is $headers->referrer,                  'bar', 'right value';
};

subtest 'Clone' => sub {
  my $headers = Mojo::Headers->new;
  $headers->add(Connection => 'close');
  $headers->add(Connection => 'keep-alive');
  is $headers->header('Connection'), 'close, keep-alive', 'right value';
  my $clone = $headers->clone;
  $headers->connection('nothing');
  is $headers->header('Connection'), 'nothing',           'right value';
  is $clone->header('Connection'),   'close, keep-alive', 'right value';
  $headers = Mojo::Headers->new;
  $headers->expect('100-continue');
  is $headers->expect, '100-continue', 'right value';
  $clone = $headers->clone;
  $clone->expect('nothing');
  is $headers->expect, '100-continue', 'right value';
  is $clone->expect,   'nothing',      'right value';
  $clone = Mojo::Headers->new->add(Foo => [qw(bar baz)])->clone;
  is_deeply $clone->to_hash(1)->{Foo}, [[qw(bar baz)]], 'right structure';
  $clone = $headers->clone;
  $headers->add(Expect => 'whatever');
  is_deeply $clone->to_hash(1),   {Expect => ['100-continue']},             'right structure';
  is_deeply $headers->to_hash(1), {Expect => ['100-continue', 'whatever']}, 'right structure';
};

subtest 'Parse headers' => sub {
  my $headers = Mojo::Headers->new;
  isa_ok $headers->parse(<<'EOF'), 'Mojo::Headers', 'right return value';
Content-Type: text/plain
o: x
Expect: 100-continue
Cache-control: public
Expires: Thu, 01 Dec 1994 16:00:00 GMT

EOF
  ok $headers->is_finished, 'parser is finished';
  is $headers->content_type,  'text/plain',                    'right value';
  is $headers->expect,        '100-continue',                  'right value';
  is $headers->cache_control, 'public',                        'right value';
  is $headers->expires,       'Thu, 01 Dec 1994 16:00:00 GMT', 'right value';
  is $headers->header('o'),   'x',                             'right value';
};

subtest 'Parse multi-line headers' => sub {
  my $headers = Mojo::Headers->new;
  $headers->parse(<<'EOF');
Foo: first
 second
 third
Content-Type: text/plain
Foo Bar: baz
Foo: first again
  second ":again"

EOF
  ok $headers->is_finished, 'parser is finished';
  my $hash = {
    'Content-Type' => ['text/plain'],
    Foo            => ['first second third', 'first again second ":again"'],
    'Foo Bar'      => ['baz']
  };
  is_deeply $headers->to_hash(1), $hash, 'right structure';
  is $headers->header('Foo'), 'first second third, first again second ":again"', 'right value';
  $headers = Mojo::Headers->new->parse($headers->to_string . "\x0d\x0a\x0d\x0a");
  ok $headers->is_finished, 'parser is finished';
  is_deeply $headers->to_hash(1), $hash, 'successful roundtrip';
  $hash
    = {'Content-Type' => 'text/plain', Foo => 'first second third, first again second ":again"', 'Foo Bar' => 'baz'};
  is_deeply $headers->to_hash, $hash, 'right structure';
};

subtest 'Set headers from hash' => sub {
  my $headers = Mojo::Headers->new;
  $headers->from_hash({Connection => 'close', 'Content-Type' => 'text/html'});
  is_deeply $headers->to_hash, {Connection => 'close', 'Content-Type' => 'text/html'}, 'right structure';
};

subtest 'Remove all headers' => sub {
  my $headers = Mojo::Headers->new;
  $headers->from_hash({});
  is_deeply $headers->to_hash, {}, 'right structure';
};

subtest 'Append values' => sub {
  my $headers = Mojo::Headers->new;
  $headers->vary('Accept');
  $headers->append(Vary => 'Accept-Encoding');
  is $headers->vary, 'Accept, Accept-Encoding', 'right value';
  $headers = Mojo::Headers->new;
  $headers->append(Vary => 'Accept');
  is $headers->vary, 'Accept', 'right value';
  $headers->append(Vary => 'Accept-Encoding');
  is $headers->vary, 'Accept, Accept-Encoding', 'right value';
  $headers = Mojo::Headers->new;
  $headers->add(Vary => 'Accept', 'Accept-Encoding');
  is_deeply $headers->to_hash(1), {Vary => ['Accept', 'Accept-Encoding']}, 'right structure';
  $headers->append(Vary => 'Accept-Language');
  is_deeply $headers->to_hash(1), {Vary => ['Accept, Accept-Encoding, Accept-Language']}, 'right structure';
};

subtest 'Multiple headers with the same name' => sub {
  my $headers = Mojo::Headers->new;
  $headers->from_hash({'X-Test' => [23, 24], 'X-Test2' => 'foo', Connection => ['a', 'b']});
  my $hash = $headers->to_hash;
  is $hash->{'X-Test'},   '23, 24', 'right value';
  is $hash->{'X-Test2'},  'foo',    'right value';
  is $hash->{Connection}, 'a, b',   'right value';
  $hash = $headers->to_hash(1);
  is_deeply $hash->{'X-Test'},   [23, 24],   'right structure';
  is_deeply $hash->{'X-Test2'},  ['foo'],    'right structure';
  is_deeply $hash->{Connection}, ['a', 'b'], 'right structure';
  $headers = Mojo::Headers->new->parse($headers->to_string . "\x0d\x0a\x0d\x0a");
  is_deeply $headers->to_hash(1), {'X-Test' => [23, 24], 'X-Test2' => ['foo'], Connection => ['a', 'b']},
    'right structure';
  is $headers->header('X-Test'),                      '23, 24',  'right value';
  is $headers->connection,                            'a, b',    'right value';
  is $headers->connection('c', 'd', 'e')->connection, 'c, d, e', 'right value';
};

subtest 'Headers in chunks' => sub {
  my $headers = Mojo::Headers->new;
  isa_ok $headers->parse(<<EOF), 'Mojo::Headers', 'right return value';
Content-Type: text/plain
EOF
  ok !$headers->is_finished,           'parser is not finished';
  ok !defined($headers->content_type), 'no value';
  isa_ok $headers->parse(<<EOF), 'Mojo::Headers', 'right return value';
X-Bender: Bite my shiny
EOF
  ok !$headers->is_finished,         'parser is not finished';
  ok !defined($headers->connection), 'no value';
  isa_ok $headers->parse(<<EOF), 'Mojo::Headers', 'right return value';
X-Bender: metal ass!

EOF
  ok $headers->is_finished, 'parser is finished';
  is $headers->content_type,       'text/plain',                'right value';
  is $headers->header('X-Bender'), 'Bite my shiny, metal ass!', 'right value';
};

subtest 'Dehop' => sub {
  my $headers = Mojo::Headers->new;
  my $fail    = {
    Connection            => 'fail',
    'Keep-Alive'          => 'fail',
    'Proxy-Authenticate'  => 'fail',
    'Proxy-Authorization' => 'fail',
    Server                => 'pass',
    TE                    => 'fail',
    Trailer               => 'fail',
    'Transfer-Encoding'   => 'fail',
    Upgrade               => 'fail'
  };
  $headers->from_hash($fail);
  is_deeply $headers->to_hash, $fail, 'right structure';
  is_deeply $headers->dehop->to_hash, {Server => 'pass'}, 'right structure';
};

subtest 'Links' => sub {
  my $headers = Mojo::Headers->new;
  is_deeply $headers->links, {}, 'right structure';

  $headers->link('</foo>; rel=foo, </bar>; rel=foo');
  is_deeply $headers->links, {foo => {link => '/foo', rel => 'foo'}}, 'right structure';

  $headers->link('</foo>; rel=foo; title=bar');
  is_deeply $headers->links, {foo => {link => '/foo', rel => 'foo', title => 'bar'}}, 'right structure';

  $headers->link('<http://example.com?foo=b;,ar>; rel=next, </>; rel=root; title="foo bar"');
  my $links = {
    next => {link => 'http://example.com?foo=b;,ar', rel => 'next'},
    root => {link => '/', rel => 'root', title => 'foo bar'}
  };
  is_deeply $headers->links, $links, 'right structure';

  is_deeply $headers->link('</foo>')->links,              {}, 'right structure';
  is_deeply $headers->link('</foo>;')->links,             {}, 'right structure';
  is_deeply $headers->link('</foo>; test=failed')->links, {}, 'right structure';
  is_deeply $headers->link('test=failed')->links,         {}, 'right structure';

  $headers->links({next => '/foo', prev => '/bar'});
  is_deeply $headers->to_hash, {Link => '</foo>; rel="next", </bar>; rel="prev"'}, 'right structure';

  $headers->links({next => '/foo?bar'});
  is_deeply $headers->to_hash, {Link => '</foo?bar>; rel="next"'}, 'right structure';

  $headers->links({next => '/foo?ba;,r'});
  is_deeply $headers->to_hash, {Link => '</foo?ba;,r>; rel="next"'}, 'right structure';
};

subtest 'Invalid characters' => sub {
  my $headers = Mojo::Headers->new;
  eval { $headers->add(Foo => "test\x0a") };
  like $@, qr/Invalid characters in Foo header/, 'right error';
  eval { $headers->header(Foo => "test\x0d") };
  like $@, qr/Invalid characters in Foo header/, 'right error';
  eval { $headers->append(Foo => "\x0atest") };
  like $@, qr/Invalid characters in Foo header/, 'right error';
  eval { $headers->header(Bar => "\x0a\x0dtest") };
  like $@, qr/Invalid characters in Bar header/, 'right error';
};

done_testing();
