# Copyright (C) 2007 Warp Networks S.L.
# Copyright (C) 2008-2013 Zentyal S.L.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2, as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
use strict;
use warnings;

package EBox::MailFilter::VDomainsLdap;

use base qw(EBox::LdapUserBase EBox::LdapVDomainBase);

use EBox::Sudo;
use EBox::Global;
use EBox::Ldap;
use EBox::Gettext;
use EBox::MailVDomainsLdap;
use EBox::MailAliasLdap;
use EBox::MailFilter::Types::AntispamThreshold;
use EBox::Samba::User;
use EBox::Exceptions::External;
use EBox::Exceptions::Internal;

# LDAP schema
use constant SCHEMAS => ('/etc/ldap/schema/amavis.schema',
                         '/etc/ldap/schema/eboxfilter.schema');

sub new
{
    my $class = shift;
    my $self  = {};
    $self->{ldap} =  EBox::Global->modInstance('samba')->ldap();
    bless($self, $class);
    return $self;
}

sub _moduleConfigured
{
    my ($self) = @_;
    my $mf =  EBox::Global->modInstance('mailfilter');

    return $mf->configured();
}

sub _vdomainAttr
{
    my ($self, $vdomain, $attr) = @_;

    my %args = (
                base => $self->vdomainTreeDn(),
                filter => 'virtualdomain=' . $vdomain,
                scope => 'one',
                attrs => ["$attr"],
               );

    my $result = $self->{ldap}->search(\%args);

    my $entry = $result->entry(0);
    defined $entry or return undef;

    my @values = $entry->get_value($attr);
    if (wantarray) {
        return @values;
    } else {
        return $values[0];
    }
}

sub _vdomainBoolAttr
{
    my $value = _vdomainAttr(@_);

    if (defined $value) {
        if ($value eq 'TRUE') {
            return 1;
        } elsif ($value eq 'FALSE') {
            return 0;
        } else {
            throw EBox::Exceptions::Internal ("A bool attr must return either FALSE or TRUE (waas $value)");
        }
    } else {
        return undef;
    }
}

sub _setVDomainAttr
{
    my ($self, $vdomain, $attr, $value) = @_;

    my $dn =  $self->vdomainDn($vdomain);

    my $ldap = $self->{'ldap'};
    if (defined $value) {
        $ldap->modifyAttribute($dn, $attr => $value);
    } else {
        $self->_deleteVDomainAttr($vdomain, $attr);
    }

    $self->_updateVDomain($vdomain);
}

sub _setVDomainBoolAttr
{
    my ($self, $vdomain, $attr, $value) = @_;

    if (defined $value) {
        $value = $value ? 'TRUE' : 'FALSE';
    }

    $self->_setVDomainAttr($vdomain, $attr, $value);
}

sub _addVDomainAttr
{
    my ($self, $vdomain, $attr, @values) = @_;

    my $dn =  $self->vdomainDn($vdomain);
    my $ldap = $self->{'ldap'};

    my @addList;
    if (@values == 1) {
        @addList = ($attr => $values[0]);
    } else {
        @addList = ($attr => \@values)
    }

    $ldap->modify($dn, { add => [ @addList ] });

    $self->_updateVDomain($vdomain);
}

sub _deleteVDomainAttr
{
    my ($self, $vdomain, $attr, @values) = @_;

    my $dn =  $self->vdomainDn($vdomain);
    my $ldap = $self->{'ldap'};

    my @deleteParams;
    if (@values == 0) {
        @deleteParams = ($attr);
    } elsif (@values == 1) {
        @deleteParams = ($attr  => $values[0]);
    } else {
        @deleteParams = ($attr => \@values);
    }

    $ldap->modify($dn, { delete => [ @deleteParams ] });

    $self->_updateVDomain($vdomain);
}

sub whitelist
{
    my ($self, $vdomain) = @_;
    my @wl = $self->_vdomainAttr($vdomain, 'amavisWhitelistSender');
    return @wl;
}

sub setWhitelist
{
    my ($self, $vdomain, $senderList_r) = @_;
    $self->_setSenderList($vdomain, 'amavisWhitelistSender', $senderList_r);
}

sub blacklist
{
    my ($self, $vdomain) = @_;
    my @wl = $self->_vdomainAttr($vdomain, 'amavisBlacklistSender');
    return @wl;
}

sub setBlacklist
{
    my ($self, $vdomain, $senderList_r) = @_;
    $self->_setSenderList($vdomain, 'amavisBlacklistSender', $senderList_r);
}

sub _setSenderList
{
    my ($self, $vdomain, $listName, $senderList_r) = @_;
    my @senderList = @{ $senderList_r };

    # validate senders
    foreach my $sender (@senderList) {
        EBox::MailFilter::Types::AmavisSender->validate($sender);
    }

    # remove old list
    if ($self->_vdomainAttr($vdomain, $listName)) {
        $self->_deleteVDomainAttr($vdomain, $listName);
    }

    # set new list
    if (@senderList) {
        $self->_addVDomainAttr($vdomain, $listName, @senderList);
    }
}

# Method: spamThreshold
#
#  get the spam threshold for the vdomain. Please note than in the actual
#  implementation amavisSpamTag2Level and amavisSpamKillLevel are setted to the
#  same value
sub spamThreshold
{
    my ($self, $vdomain) = @_;
    my $threshold = $self->_vdomainAttr($vdomain, 'amavisSpamTag2Level');
    return $threshold;
}

# Method: setSpamThreshold
#
#  set the spam threshold for the vdomain. Please note than in the actual
#  implementation amavisSpamTag2Level and amavisSpamKillLevel are setted to the
#  same value
sub setSpamThreshold
{
    my ($self, $vdomain, $threshold) = @_;

    my $dn =  $self->vdomainDn($vdomain);

    $self->_updateVDomain($vdomain);

    my $ldap = $self->{'ldap'};
    if (defined $threshold) {
        $ldap->modifyAttribute($dn,  'amavisSpamTag2Level' => $threshold);
        $ldap->modifyAttribute($dn,  'amavisSpamKillLevel' => $threshold);
    } else {
        my @toDelete;
        foreach my $attr (qw(amavisSpamTag2Level amavisSpamKillLevel)) {
            # if attribute exists, mark for deletion
            if ($self->_vdomainAttr($vdomain, $attr)) {
                push @toDelete, $attr;
            }
        }

        # if we dont have nothing to delete end here
        @toDelete or
            return;

        $ldap->modify( $dn, { delete =>  \@toDelete });
    }
}

sub antispam
{
    my ($self, $vdomain) = @_;
    my $value = $self->_vdomainBoolAttr($vdomain, 'amavisBypassSpamChecks');
    $value = $value ? 0 : 1;  # the ldap attribute has reverse logic..
    return $value;
}

sub setAntispam
{
    my ($self, $vdomain, $value) = @_;

    $value = $value ? 0 : 1;  # the ldap attribute has reverse logic..

    $self->_setVDomainBoolAttr($vdomain, 'amavisBypassSpamChecks', $value);
}

sub antivirus
{
    my ($self, $vdomain) = @_;
    my $value =  $self->_vdomainBoolAttr($vdomain, 'amavisBypassVirusChecks');
    $value = $value ? 0 : 1;  # the ldap attribute has reverse logic..
    return $value;
}

sub setAntivirus
{
    my ($self, $vdomain, $value) = @_;

    $value = $value ? 0 : 1;  # the ldap attribute has reverse logic..

    $self->_setVDomainBoolAttr($vdomain, 'amavisBypassVirusChecks', $value);
}

sub _addVDomain
{
    my ($self, $vdomain) = @_;

    return unless ($self->_moduleConfigured());

    my $ldap = $self->{ldap};
    my $dn =  $self->vdomainDn($vdomain);

    if (not $ldap->isObjectClass($dn, 'vdmailfilter')) {
        my %attrs = (
                     changes => [
                                 add => [ objectClass       => 'amavisAccount', ],
                                ],
                    );

        $ldap->modify($dn, \%attrs );

        %attrs = (
                     changes => [
                                 add => [ objectClass       => 'vdmailfilter',  ],
                                ],
                    );

        $ldap->modify($dn, \%attrs );

        # %attrs = (
        #              changes => [
        #                          add => [ domainMailPortion => "\@$vdomain",  ],
        #                         ],
        #             );
        # $ldap->modify($dn, \%attrs );

        use EBox::Samba::LdapObject;
        my $ob = EBox::Samba::LdapObject->new(dn => $dn);
        $ob->add(domainMailPortion => "\@$vdomain");
    }

    $self->_vdomainsListChanged();
}

# Method: spamAccount
#
#  Parameters:
#    vdomain
#
#  Returns :
#   the spam account for the vdomain or false if it has not spam account
sub spamAccount
{
    return undef;
    # my ($self, $vdomain) = @_;
    # my $usersMod = EBox::Global->modInstance('samba');
    # my $user = $usersMod->userByUID('spam');
    # return $self->_hasAccount($vdomain, $user);
}

# Method: hamAccount
#
#  Parameters:
#    vdomain
#
#  Returns :
#   the ham account for the vdomain or false if it has not ham account
sub hamAccount
{
    return undef;
    # my ($self, $vdomain) = @_;
    # my $usersMod = EBox::Global->modInstance('samba');
    # my $user = $usersMod->userByUID('ham');
    # return $self->_hasAccount($vdomain, $user);
}

sub learnAccountsExists
{
    my ($self) = @_;
    return 0;

    # my @vdomains =  $self->vdomains() ;
    # foreach my $vdomain (@vdomains) {
    #     if ($self->spamAccount($vdomain) or $self->hamAccount($vdomain)) {
    #         return 1;
    #     }
    # }

    # return 0;
}

# Method: learnAccounts
#
#  Parameters:
#    vdomain
#
#  Returns :
#   list which the learn accounts for the vdomain
sub learnAccounts
{
    return [];
    # my ($self, $vdomain) = @_;
    # $self->checkVDomainExists($vdomain);

    # my @accounts;
    # my $hamAccount = $self->hamAccount($vdomain);
    # if ($hamAccount) {
    #     push @accounts, $hamAccount;
    # }
    # my $spamAccount = $self->spamAccount($vdomain);
    # if ($spamAccount) {
    #     push @accounts, $spamAccount;
    # }

    # return \@accounts;
}

sub _hasAccount
{
    my ($self, $vdomain, $user) = @_;
    return 0; # learn accounts disabled by now

    # return 0 unless ($user);

    # my $mail         = EBox::Global->modInstance('mail');
    # my $mailUserLdap = $mail->_ldapModImplementation();
    # my $mailAliasLdap = new EBox::MailAliasLdap;

    # my $account = $mailUserLdap->userAccount($user);
    # if (not defined $account) {
    #     # control account not defined in any domain
    #     return 0;
    # }

    # my ($lh, $accountVdomain) = split '@', $account;

    # if ($vdomain eq $accountVdomain) {
    #     # this domain has the control account itseldf
    #     return $account;
    # }

    # my $alias = $user->name() . '@' . $vdomain;
    # if ($mailAliasLdap->aliasExists($alias)) {
    #     return $alias;
    # }

    # # neither account itself or alias in this domain
    # return 0;
}

sub setSpamAccount
{
    # Learn accounts disabled by now
    # my ($self, $vdomain, $active) = @_;
    # my $usersMod = EBox::Global->modInstance('samba');
    # my $user = $usersMod->userByUID('spam');
    # $self->_setAccount($vdomain, $user, $active);
}

sub setHamAccount
{
    # Learn accounts disabled by now
    # my ($self, $vdomain, $active) = @_;
    # my $usersMod = EBox::Global->modInstance('samba');
    # my $user = $usersMod->userByUID('ham');
    # $self->_setAccount($vdomain, $user, $active);
}

sub _setAccount
{
    my ($self, $vdomain, $user, $active) = @_;

    return unless ($user);

    if ($active) {
        $self->_addAccount($vdomain, $user);
    } else {
        $self->_removeAccount($vdomain, $user);
    }
}

sub _addAccount
{
    my ($self, $vdomain, $user) = @_;

    my $mail         = EBox::Global->modInstance('mail');
    my $mailUserLdap = $mail->_ldapModImplementation();
    my $mailAliasLdap = new EBox::MailAliasLdap;

    my $username = $user->name();
    my $account = $mailUserLdap->userAccount($user);
    if (defined $account) {
        my ($lh, $accountVdomain) = split ('@', $account);

        if ($vdomain eq $accountVdomain) {
            # this domain has the account so we haven't nothing to do
            return;
        }

        my $alias = $username . '@' . $vdomain;
        if (not $mailAliasLdap->aliasExists($alias)) {
            $mailAliasLdap->addUserAlias($user, $alias);
        }
    } else {
        $mailUserLdap->setUserAccount($user, $username, $vdomain);
    }
}

sub _removeAccount
{
    my ($self, $vdomain, $user) = @_;

    my $mail         = EBox::Global->modInstance('mail');
    my $mailUserLdap = $mail->_ldapModImplementation();

    my $account = $mailUserLdap->userAccount($user);
    defined $account or
        return;

    my @vdomains = grep {
        ($_ ne $vdomain) and $self->_hasAccount($_, $user)
    } $self->vdomains();

    # remove account and all its addresses
    $mailUserLdap->delUserAccount($user, $account);
    # add account for domains which need it
    foreach my $vd (@vdomains) {
        $self->_addAccount($vd, $user);
    }
}

sub _delVDomain
{
    my ($self, $vdomain) = @_;

    return unless ($self->_moduleConfigured());

    # remove ham and spam accounts
    $self->setSpamAccount($vdomain, 0);
    $self->setHamAccount($vdomain, 0);

    # remove from ldap if neccesary
    my $ldap = $self->{ldap};
    my $dn =  $self->vdomainDn($vdomain);

    if ( $ldap->isObjectClass($dn, 'vdmailfilter')) {
        $ldap->delObjectclass($dn, 'vdmailfilter');
    }

    $self->_vdomainsListChanged();
}

sub _modifyVDomain
{
}

sub _delVDomainWarning
{
}

sub _vdomainsListChanged
{
    my ($self) = @_;

    my $mf =  EBox::Global->modInstance('mailfilter');
    my $smtpFilter = $mf->smtpFilter();
    # only the smtp filter needs to renerate its config
    if ($smtpFilter->isEnabled()) {
        EBox::Global->addModuleToPostSave('mailfilter');
    }
}

sub _includeLDAPSchemas
{
    my ($self) = @_;

    return [] unless ($self->_moduleConfigured());

    my @schemas = SCHEMAS;
    return \@schemas;
}

sub vdomains
{
    my $mailvdomain = new  EBox::MailVDomainsLdap();
    return $mailvdomain->vdomains();
}

sub vdomainDn
{
    my ($self, $vdomain) = @_;

    return "cn=$vdomain," . $self->vdomainTreeDn() ;
}

sub vdomainTreeDn
{
    my $mailvdomain = new  EBox::MailVDomainsLdap();
    return $mailvdomain->vdomainDn();
}

sub _updateVDomain
{
    my ($self, $vdomain) = @_;
    EBox::MailVDomainsLdap->new()->_updateVDomain($vdomain);
}

sub checkVDomainExists
{
    my ($self, $vdomain) = @_;
    my $mailvdomains = EBox::MailVDomainsLdap->new();
    if (not $mailvdomains->vdomainExists($vdomain)) {
        throw EBox::Exceptions::External(__x(q{Virtual mail domain {vd} does not exist},
                                             vd => $vdomain));
    }
}

# Method: resetVDomain
#
#  restore default antispam configuration for the give domain
sub resetVDomain
{
    my ($self, $vdomain) = @_;

    my $ldap = $self->{ldap};
    my $dn =  $self->vdomainDn($vdomain);

    if (not $ldap->isObjectClass($dn, 'vdmailfilter')) {
        $self->_addVDomain($vdomain);
    }

    # reset booleans to false
    my @boolMethods = qw(setAntivirus setAntispam);
    foreach my $method (@boolMethods) {
        $self->$method($vdomain, 1);
    }

    # clear non-boolean atributtes
    my @delAttrs = (
            'amavisVirusLover', 'amavisBannedFilesLover', 'amavisSpamLover',
            'amavisSpamTagLevel', 'amavisSpamTag2Level',
            'amavisSpamKillLevel', 'amavisSpamModifiesSubj',
            'amavisSpamQuarantineTo',
            'amavisWhitelistSender', 'amavisBlacklistSender'
    );
    # use only setted attributes
    @delAttrs = grep {
                       my $value = $self->_vdomainAttr($vdomain, $_);
                       defined $value;
                     } @delAttrs;

    if (@delAttrs) {
        my %delAttrs = ( delete => \@delAttrs );
        $ldap->modify($dn, \%delAttrs );
    }

    # remove ham/spam ocntrol accounts
    $self->setSpamAccount($vdomain, 0);
    $self->setHamAccount($vdomain, 0);
}

sub regenConfig
{
    my ($self) = @_;

    my %vdomainsNotConfigured = map {  $_ => 1 } $self->vdomains();

    my $mf =  EBox::Global->modInstance('mailfilter');
    my $vdomainsTable = $mf->model('VDomainsFilter');

    foreach my $id (@{ $vdomainsTable->ids() }) {
        my $vdRow = $vdomainsTable->row($id);
        my $vdomain     = $vdomainsTable->nameFromRow($vdRow);
        my $antivirus   = $vdRow->elementByName('antivirus')->value();
        my $antispam    = $vdRow->elementByName('antispam')->value();
        my $threshold   = $vdomainsTable->spamThresholdFromRow($vdRow);
        # learn accounts disabled
#        my $hamAccount  = $vdRow->elementByName('hamAccount')->value();
#        my $spamAccount = $vdRow->elementByName('spamAccount')->value();

        $self->setAntivirus($vdomain, $antivirus);
        $self->setAntispam($vdomain, $antispam);
        $self->setSpamThreshold($vdomain, $threshold);

        # $self->setHamAccount($vdomain, $hamAccount);
        # $self->setSpamAccount($vdomain, $spamAccount);

        my @whitelist;
        my @blacklist;

        my $acl = $vdRow->subModel('acl');
        foreach my $id (@{ $acl->ids()  }) {
            my $aclRow = $acl->row($id);
            my $sender = $aclRow->elementByName('sender')->value();
            my $policy = $aclRow->elementByName('policy')->value();

            if ($policy eq 'blacklist') {
                push @blacklist, $sender;
            } elsif ($policy eq 'whitelist') {
                push @whitelist, $sender;
            }
        }

        $self->setWhitelist($vdomain, \@whitelist);
        $self->setBlacklist($vdomain, \@blacklist);

        delete $vdomainsNotConfigured{$vdomain};
    }

    # vdomains no present in the table are reseted to not config state
    foreach my $vdomain (keys %vdomainsNotConfigured) {
        $self->resetVDomain($vdomain);
    }
}

1;
