# t/post.t
#
# Test posting.
#
# Authors:
#      Afuna <coder.dw@afunamatata.com>
#
# Copyright (c) 2013 by Dreamwidth Studios, LLC.
#
# This program is free software; you may redistribute it and/or modify it under
# the same terms as Perl itself.  For a copy of the license, please reference
# 'perldoc perlartistic' or 'perldoc perlgpl'.
#

use strict;
use warnings;

use Test::More tests => 104;

BEGIN { $LJ::_T_CONFIG = 1; require "$ENV{LJHOME}/cgi-bin/ljlib.pl"; }
use LJ::Test qw( temp_user temp_comm );

use Hash::MultiValue;

use DW::Auth::Challenge;
use DW::Controller::Entry;
use DW::FormErrors;
use DW::Routing::CallInfo;
use LJ::Community;
use LJ::Entry;

use FindBin qw($Bin);
chdir "$Bin/data/userpics" or die "Failed to chdir to t/data/userpics";

# preload userpics so we don't have to read the file hundreds of times
open( my $fh, 'good.png' ) or die $!;
my $ICON1 = do { local $/; <$fh> };

open( my $fh2, 'good.jpg' ) or die $!;
my $ICON2 = do { local $/; <$fh2> };

$LJ::CAP{$_}->{moodthemecreate} = 1 foreach ( 0 .. 15 );

note("Not logged in - init");
{
    my $vars = DW::Controller::Entry::_init();

    # user
    ok( !$vars->{remote} );

    # mood theme
    ok( !keys %{ $vars->{moodtheme} }, "No mood theme." );

TODO: {
        local $TODO = "usejournal";
    }
}

note("Logged in - init");
{
    my $u = temp_user();
    LJ::set_remote($u);

    my $vars;
    $vars = DW::Controller::Entry::_init( { remote => $u } );

    ok( $u->equals( $vars->{remote} ), "Post done as currently logged in user." );

    note("# Moodtheme");
    note("  default mood theme");
    $vars = DW::Controller::Entry::_init( { remote => $u } );
    my $moods = DW::Mood->get_moods;

SKIP: {
        skip "Default mood theme not defined.", 1 unless $LJ::USER_INIT{moodthemeid};
        ok( $vars->{moodtheme}->{id} == $LJ::USER_INIT{moodthemeid}, "Default mood theme." );
    }
    is( scalar keys %{ $vars->{moodtheme}->{pics} }, scalar keys %$moods, "Complete mood theme." );

    note("  no mood theme");
    $u->update_self( { moodthemeid => undef } );
    $u = LJ::load_user( $u->user, 'force' );

    $vars = DW::Controller::Entry::_init();
    ok( !%{ $vars->{moodtheme} }, "No mood theme." );

    note("  custom mood theme with incomplete moods");
    my $themeid =
        $u->create_moodtheme( "testing", "testing a custom mood theme with missing moods." );
    my $customtheme = DW::Mood->new($themeid);
    $u->update_self( { moodthemeid => $customtheme->id } );
    $u = LJ::load_user( $u->user, 'force' );

    # we used to pick a random mood here - whichever moodid was the
    # first one returned from the keys array - but the test would
    # fail if we picked a mood that had other moods inherit from it,
    # so now we pick a mood that is known to be childless
    my $testmoodid = 105;    # quixotic
    my $err;
    $customtheme->set_picture( $testmoodid,
        { picurl => "http://example.com/moodpic", width => 10, height => 20 }, \$err );

    $vars = DW::Controller::Entry::_init( { remote => $u } );
    is( $vars->{moodtheme}->{id}, $customtheme->id, "Custom mood theme." );
    is( scalar keys %{ $vars->{moodtheme}->{pics} },
        1, "Only provide picture information for moods with valid pictures." );
    is( $vars->{moodtheme}->{pics}->{$testmoodid}->{pic},
        "http://example.com/moodpic", "Confirm picture URL matches." );
    is( $vars->{moodtheme}->{pics}->{$testmoodid}->{width}, 10, "Confirm picture width matches." );
    is( $vars->{moodtheme}->{pics}->{$testmoodid}->{height}, 20,
        "Confirm picture height matches." );
    is(
        $vars->{moodtheme}->{pics}->{$testmoodid}->{name},
        $moods->{$testmoodid}->{name},
        "Confirm mood name matches."
    );

    note("Security levels ");
    $vars = DW::Controller::Entry::_init( { remote => $u } );
    is( scalar @{ $vars->{security} },   3,                               "Basic security levels" );
    is( $vars->{security}->[0]->{label}, '.select.security.public.label', "Public security" );
    is( $vars->{security}->[0]->{value}, "public",                        "Public security" );
    is( $vars->{security}->[1]->{label}, '.select.security.access.label', "Access-only security" );
    is( $vars->{security}->[1]->{value}, "access",                        "Access-only security" );
    is( $vars->{security}->[2]->{label}, '.select.security.private.label', "Private security" );
    is( $vars->{security}->[2]->{value}, "private", "Private security" );

    $u->create_trust_group( groupname => "test" );
    $vars = DW::Controller::Entry::_init( { remote => $u } );
    is( scalar @{ $vars->{security} }, 4, "Security with custom groups" );
    is( $vars->{security}->[0]->{label}, '.select.security.public.label',  "Public security" );
    is( $vars->{security}->[0]->{value}, "public",                         "Public security" );
    is( $vars->{security}->[1]->{label}, '.select.security.access.label',  "Access-only security" );
    is( $vars->{security}->[1]->{value}, "access",                         "Access-only security" );
    is( $vars->{security}->[2]->{label}, '.select.security.private.label', "Private security" );
    is( $vars->{security}->[2]->{value}, "private",                        "Private security" );
    is( $vars->{security}->[3]->{label}, '.select.security.custom.label',  "Custom security" );
    is( $vars->{security}->[3]->{value}, "custom",                         "Custom security" );
    is( @{ $vars->{customgroups} },      1,                                "Custom group list" );
    is( $vars->{customgroups}->[0]->{label}, "test" );
    is( $vars->{customgroups}->[0]->{value}, 1 );

    note("# Usejournal");
    note("  No communities.");
    $vars = DW::Controller::Entry::_init( { remote => $u } );
    is( scalar @{ $vars->{journallist} }, 1, "One journal (yourself)" );
    ok( $vars->{journallist}->[0]->equals($u), "First journal in the list is yourself." );

    my $comm_canpost = temp_comm();
    my $comm_nopost  = temp_comm();
    $u->join_community( $comm_canpost, 1, 1 );
    $u->join_community( $comm_nopost,  1, 0 );

    note("  With communities.");
    $vars = DW::Controller::Entry::_init( { remote => $u } );
    is( scalar @{ $vars->{journallist} }, 2, "Yourself and one community." );
    ok( $vars->{journallist}->[0]->equals($u), "First journal in the list is yourself." );
    ok(
        $vars->{journallist}->[1]->equals($comm_canpost),
        "Second journal in the list is a community you can post to."
    );
    ok( !$vars->{usejournal}, "No usejournal argument." );
}

note(" Usejournal - init");
{
    my $u = temp_user();
    LJ::set_remote($u);
    my $comm_canpost = temp_comm();
    my $comm_nopost  = temp_comm();
    $u->join_community( $comm_canpost, 1, 1 );
    $u->join_community( $comm_nopost,  1, 0 );

    note("  With usejournal argument (can post)");
    my $vars = DW::Controller::Entry::_init( { usejournal => $comm_canpost->user, remote => $u } );
    is( scalar @{ $vars->{journallist} }, 1, "Usejournal." );
    ok(
        $vars->{journallist}->[0]->equals($comm_canpost),
        "Only item in the list is usejournal value."
    );
    ok( $vars->{usejournal}->equals($comm_canpost), "Usejournal argument." );

    note(" checking community security levels ");
    is( scalar @{ $vars->{security} },   3,                               "Basic security levels" );
    is( $vars->{security}->[0]->{label}, '.select.security.public.label', "Public security" );
    is( $vars->{security}->[0]->{value}, "public",                        "Public security" );
    is( $vars->{security}->[1]->{label}, '.select.security.members.label',
        "Members-only security" );
    is( $vars->{security}->[1]->{value}, "access",                       "Access-only security" );
    is( $vars->{security}->[2]->{label}, '.select.security.admin.label', "Admin-only security" );
    is( $vars->{security}->[2]->{value}, "private",                      "Private security" );

    # TODO:
    # tags ( fetched by JS )
    # mood, icons, comments, age restriction don't change

    # crosspost: shouldn't show up? or is that another thing to be hidden by JS?

    # allow this, because the user can still log in as another user in order to complete the post
    note("  With usejournal argument (cannot post)");
    $vars = DW::Controller::Entry::_init( { usejournal => $comm_nopost->user, remote => $u } );
    is( scalar @{ $vars->{journallist} }, 1, "Usejournal." );
    ok(
        $vars->{journallist}->[0]->equals($comm_nopost),
        "Only item in the list is usejournal value."
    );
    ok( $vars->{usejournal}->equals($comm_nopost), "Usejournal argument." );
}

note("Altlogin - init");
{
    my $u   = temp_user();
    my $alt = temp_user();
    LJ::set_remote($u);

    my $vars = DW::Controller::Entry::_init( { altlogin => 1 } );

    ok( !$vars->{remote}, "Altlogin means form has no remote" );
    ok( !$vars->{poster}, "\$alt doesn't show up in the form on init" );

    ok( !$vars->{tags},                "No tags" );
    ok( !keys %{ $vars->{moodtheme} }, "No mood theme." );
    is( @{ $vars->{security} }, 3, "Default security dropdown" );
    ok( !@{ $vars->{journallist} }, "No journal dropdown" );

    # TODO:
    # comments
    # age restriction
    # crosspost
    # scheduled
}

my $postdata = {
    subject => "here is a subject",
    event   => "here is some event data",
};

my $postdecoded_bare = {
    event         => $postdata->{event},
    subject       => undef,
    sticky_select => undef,
    slug          => '',

    security  => 'public',
    allowmask => 0,

    crosspost_entry    => 0,
    sticky_entry       => undef,
    update_displaydate => undef,

    props => {
        taglist => "",
        editor  => '',

        opt_nocomments => 0,
        opt_noemail    => 0,
        opt_screening  => '',

        opt_preformatted => 0,
        opt_backdated    => 0,

        adult_content        => '',
        adult_content_reason => '',

        current_location => '',
        current_music    => '',
        current_mood     => '',
        current_moodid   => '',

        admin_post => 0,
    }
};

note("Not logged in - post");
TODO: {
    local $TODO = "Handle not logged in (post)";
}

note("Not logged in - post (community)");
TODO: {
    local $TODO = "post to a community while not logged in";
}

sub post_with {
    my %opts = @_;

    my $remote = temp_user();
    LJ::set_remote($remote);

    $opts{event} = $postdata->{event} unless exists $opts{event};
    $opts{chal}  = DW::Auth::Challenge->generate;

    # if we'd been in a handler, this would have been put into $vars->{formdata}
    # and automatically converted to Hash::MultiValue. We're not, though, so fake it
    my $post = Hash::MultiValue->from_mixed( \%opts );

    my %flags;
    my %auth;
    %auth = DW::Controller::Entry::_auth( \%flags, $post, $remote, $LJ::SITEROOT );

    my %req;
    my $errors = DW::FormErrors->new;
    DW::Controller::Entry::_form_to_backend( \%req, $post, errors => $errors );

    my $res = DW::Controller::Entry::_save_new_entry( \%req, \%flags, \%auth );
    delete $req{props}->{unknown8bit};    # TODO: remove this from protocol at some point

    return ( \%req, $res, $remote, $errors );
}

note("Logged in - post (bare minimum)");
{
    # only have the event
    my ( $req, $res, $u, $errors ) = post_with( undef => undef );
    is_deeply( $req, $postdecoded_bare, "decoded entry form" );
    is_deeply( $errors->get_all, [], "no errors" );

    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
    is( $entry->subject_orig, '', "subject" );
    is( $entry->event_orig, $postdata->{event}, "event text" );
}

note("Post - subject");
{
    my ( $req, $res, $u, $errors ) = post_with( subject => $postdata->{subject} );
    is_deeply( $req, { %$postdecoded_bare, subject => $postdata->{subject}, } );
    is_deeply( $errors->get_all, [], "no errors" );

    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
    is( $entry->subject_orig, $postdata->{subject} );
    is( $entry->event_orig,   $postdata->{event} );
}

note("Post - lacking required info");
{
    my ( $req, $res, $u, $errors ) = post_with( subject => $postdata->{subject}, event => undef );
    is_deeply(
        $req,
        {
            %$postdecoded_bare,
            subject => $postdata->{subject},
            event   => "",
        },
        "decoded entry form"
    );
    my $error_list = $errors->get_all;
    is( scalar @$error_list, 1, "one error returned" );
    is( $error_list->[0]->{'ml_key'}, '.error.noentry', "no entry text" );

    is_deeply(
        $res,
        {
            errors => LJ::Protocol::error_message(200),
        },
        "failed, lacking required arguments"
    );
}

note("Post - security:public");
{
    my ( $req, $res, $u ) = post_with( security => "" );
    is_deeply( $req, { %$postdecoded_bare, security => "public", }, "decoded entry form" );

    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
    is( $entry->security, "public", "Public security" );

    ( $req, $res, $u ) = post_with( security => "public" );
    is_deeply( $req, { %$postdecoded_bare, security => "public", }, "decoded entry form" );

    $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
    is( $entry->security, "public", "Public security" );
}

note("Post - security:access");
{
    my ( $req, $res, $u ) = post_with( security => "access" );
    is_deeply(
        $req,
        {
            %$postdecoded_bare,
            security  => "usemask",
            allowmask => 1,
        },
        "decoded entry form for access-locked entry"
    );

    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
    is( $entry->security, "usemask", "Locked security" );
}

note("Post - security:private");
{
    my ( $req, $res, $u ) = post_with( security => "private" );
    is_deeply(
        $req,
        { %$postdecoded_bare, security => "private" },
        "decoded entry form for private entry"
    );

    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
    is( $entry->security, "private", "Private security" );
}

note("Post - security:custom");
{
    my ( $req, $res, $u ) = post_with( security => "custom", custom_bit => [ 1, 2 ] );
    is_deeply(
        $req,
        {
            %$postdecoded_bare,
            security  => "usemask",
            allowmask => 6,
        },
        "decoded entry form for entry with custom security"
    );

    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
    is( $entry->security,  "usemask", "Custom security" );
    is( $entry->allowmask, 6,         "Custom security allowmask" );
}

note("Post - security:custom no allowmask");
{
    my ( $req, $res, $u ) = post_with( security => "custom" );
    is_deeply(
        $req,
        {
            %$postdecoded_bare,
            security  => "usemask",
            allowmask => 0,
        },
        "decoded entry form for entry with custom security"
    );

    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
    is( $entry->security,  "usemask", "Custom security" );
    is( $entry->allowmask, 0,         "Custom security allowmask" );
}

note("Post - security:public, but with allowmask (probably changed their mind)");
{
    my ( $req, $res, $u ) = post_with( security => "public", custom_bit => [ 1, 2 ] );
    is_deeply(
        $req,
        {
            %$postdecoded_bare,
            security  => "public",
            allowmask => 0,
        },
        "decoded entry form"
    );

    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
    is( $entry->security,  "public", "Public security, not custom" );
    is( $entry->allowmask, 0,        "No custom security allowmask" );
}

note("Post - currents");
{
    my ( $req, $res, $u ) = post_with(
        current_mood       => 1,
        current_mood_other => "etc",
        current_location   => "beside the thing",
        current_music      => "things that go bump in the night"
    );
    is_deeply(
        $req,
        {
            %$postdecoded_bare,
            props => {
                %{ $postdecoded_bare->{props} },
                current_moodid   => 1,
                current_mood     => "etc",
                current_music    => "things that go bump in the night",
                current_location => "beside the thing",
            }
        },
        "decoded entry form with metadata"
    );

    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
    is( $entry->prop("current_moodid"),   1 );
    is( $entry->prop("current_mood"),     "etc" );
    is( $entry->prop("current_music"),    "things that go bump in the night" );
    is( $entry->prop("current_location"), "beside the thing" );
    is( $entry->prop("current_coords"),   undef );
}

note("Post - mood_other matches mood with a moodid");
{
    my $moodid          = 1;
    my $mood_other_id   = 2;
    my $mood_other_name = DW::Mood->mood_name($mood_other_id);

    my ( $req, $res, $u ) = post_with(
        current_moodid     => $moodid,
        current_mood_other => $mood_other_name
    );
    is_deeply(
        $req,
        {
            %$postdecoded_bare,
            props => { %{ $postdecoded_bare->{props} }, current_moodid => $mood_other_id }
        },
        "decoded entry form with metadata"
    );

    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
    is( $entry->prop("current_moodid"), $mood_other_id );
    is( $entry->prop("current_mood"),   undef );
}

note("Post - with tags, nothing fancy");
{
    my ( $req, $res, $u ) = post_with( taglist => "foo, bar, baz", );
    is_deeply(
        $req,
        {
            %$postdecoded_bare,
            props => { %{ $postdecoded_bare->{props} }, taglist => "foo, bar, baz" }
        },
        "decoded entry form with metadata"
    );

    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
    is( $entry->prop("taglist"), "foo, bar, baz", "tag list as string prop" );
    is_deeply(
        { map { $_ => 1 } $entry->tags },
        { map { $_ => 1 } qw( bar baz foo ) },
        "tag list as parsed array (order doesn't matter)"
    );
}

note("Post - setting non-default editor format");
{
    my ( $req, $res, $u ) = post_with( editor => 'markdown0', );
    is_deeply(
        $req,
        {
            %$postdecoded_bare, props => { %{ $postdecoded_bare->{props} }, editor => 'markdown0' }
        },
        "decoded entry form with metadata"
    );

    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
    is( $entry->prop("editor"), "markdown0", "specified format in saved editor prop" );
}

note("Post - setting invalid editor format");
{
    my ( $req, $res, $u ) = post_with( editor => 'morkdawn0', );
    is_deeply(
        $req,
        {
            %$postdecoded_bare, props => { %{ $postdecoded_bare->{props} }, editor => '' }
        },
        "decoded entry form with metadata"
    );

    my $entry = LJ::Entry->new( $u, jitemid => $res->{itemid} );
    is( $entry->prop("editor"), undef, "undef in saved editor prop" );
}

note("Logged in - post (community)");
TODO: {
    local $TODO = "post to a community";
}

note("Altlogin with remote - no longer allowed");
{
    my $alt      = temp_user();
    my $alt_pass = "abc123!" . rand();
    $alt->set_password($alt_pass);

    my ( $req, $res, $remote, $errors ) = post_with(
        username => $alt->user,
        password => $alt_pass
    );

    ok( !$remote->equals($alt), "Remote and altlogin aren't the same user" );

    my $entry = LJ::Entry->new( $alt, jitemid => $res->{itemid} );
    ok( !$entry->valid, "didn't post to alt" );

    my $no_entry = LJ::Entry->new( $remote, jitemid => $res->{itemid} );
    ok( $no_entry->valid, "posted to remote instead" );

}

note("Editing a draft");
TODO: {
    local $TODO = "Editing a draft";
}

# note( "Editing an entry with the wrong ditemid" );
# {
#     my ( $req, $res, $u, $errors ) = post_with( undef => undef );
#     is_deeply( $req, $postdecoded_bare, "decoded entry form" );
#     is_deeply( $errors->get_all, [], "no errors" );

#     my $anum_fake = $res->{anum} == 0 ? $res->{anum} + 1 : $res->{anum} - 1;
#     my $ditemid_fake = $res->{ditemid} * 256 + $anum_fake;

#     my $rq = HTTP::Request->new( GET => "$LJ::SITEROOT/entry/".$u->username."/$ditemid_fake/edit" );
#     my $r = DW::Request::Standard->new( $rq );
#     DW::Controller::Entry::_edit( {}, $u->username, $ditemid_fake );
# }

note("Editing an existing entry");
TODO: {
    local $TODO = "Editing an existing entry";
}

note("openid - post");
TODO: {
    my $u = temp_user( journaltype => "I" );
    LJ::set_remote($u);

    my $vars;
    $vars = DW::Controller::Entry::_init( { remote => $u } );
}

note("openid - edit");
TODO: {
    local $TODO = "Editing an existing entry as openid";
}

done_testing();

1;
