package User::User;

# JPK !!! This module is deprecated !!!

use strict;

#use warnings;
use Carp;
use Digest::MD5 qw(md5_hex);

use Data::Dumper;

use lib '/app/tools/common/lib';
use Common::TiedCode;
use Common::RSDB;
use Common::Consts;

use lib '/app/tools/data_classes/lib';
use User::Phone;
use Client::Client;

use constant DB_TABLE => "user";

use constant ACCESS_RSADMIN         => 1;
use constant ACCESS_RSREP           => 2;
use constant ACCESS_CLIENTADMIN     => 3;
use constant ACCESS_CLIENTREP       => 4;
use constant ACCESS_CLIENTUSER      => 5;
use constant ACCESS_CLIENTMARKETING => 6;

# private attributes
my @attributes = qw(user_id
  email
  first_name
  last_name
  address_id
  phone_id
  primary_contact_id
  secondary_contact_id
  disabled
  date_created
  date_modified);

my @xml_attributes = qw(UserID
  Email
  FirstName
  LastName
  AddressID
  PhoneID
  PrimaryContactID
  SecondaryContactID
  AccessLevel
  LabelID
  Disabled
  DateCreated
  DateModified
);

use Item;
use base 'Item';

# --------------------------------
# Constructor
# --------------------------------
sub new {
    my $class = shift;
    my %args  = @_;

    # JPK - This ends up instantiating a Common::RSDB object as well.
    # RPS modified RSDB.pm to not allow 'blank' client_ids.
    # That broke this code right here.
    # So, going to pass in the client id, from the ENV hash.
    #
    if ( !defined $args{client_id} ) {
        $args{client_id} = $ENV{CLIENT_ID};
    }
    my $self = $class->SUPER::new(%args);

    # we want the global db, so this will
    # override what the base class set up.
    $self->{dbo} = new Common::RSDB( client_id => 0 );
    my $retval = $self->_init(%args);

    return undef unless ($retval);
    return $self;
}

sub _init {
    my $self = shift;
    my %args = @_;

    $self->{client_id} = ( $args{client_id} >= 0 ) ? $args{client_id} : $ENV{CLIENT_ID};

    # initialize user properties
    foreach (@attributes) {
        $self->{$_} = undef;
    }

    # initialize user objects
    $self->{errstr}            = undef;
    $self->{dirty}             = 0;
    $self->{dirty_user_access} = 0;

    my $retval = 1;

    # are we loading an existing User?
    if ( $args{email} ) {
        $retval = $self->Load( email => $args{email} );
    } elsif ( $args{user_id} ) {
        $retval = $self->Load( user_id => $args{user_id} );
    }
    return $retval;
}

# --------------------------------
# Properties
# --------------------------------

# cannot set
sub UserID {
    my $self = shift;
    return $self->{user_id};
}

sub Email {
    my $self = shift;
    if (@_) {
        my ($new_val) = @_;
        if ( $new_val ne $self->{'email'} ) {
            $self->{'email'} = $new_val;
            $self->{'dirty'} = 1;
        }
    }
    return $self->{'email'};
}

sub FirstName {
    my $self = shift;
    if (@_) {
        my ($new_val) = @_;
        if ( $new_val ne $self->{'first_name'} ) {
            $self->{'first_name'} = $new_val;
            $self->{'dirty'}      = 1;
        }
    } else {
        return $self->{'first_name'};
    }
}

sub LastName {
    my $self = shift;
    if (@_) {
        my ($new_val) = @_;
        if ( $new_val ne $self->{'last_name'} ) {
            $self->{'last_name'} = $new_val;
            $self->{'dirty'}     = 1;
        }
    } else {
        return $self->{'last_name'};
    }
}

sub AddressID {
    my $self = shift;
    if (@_) {
        my ($new_val) = @_;
        if ( $new_val ne $self->{'address_id'} ) {
            $self->{'address_id'} = $new_val;
            $self->{'dirty'}      = 1;
        }
    } else {
        return $self->{'address_id'};
    }
}

sub PhoneID {
    my $self = shift;
    if (@_) {
        my ($new_val) = @_;
        if ( $new_val ne $self->{'phone_id'} ) {
            $self->{'phone_id'} = $new_val;
            $self->{'dirty'}    = 1;
        }
    } else {
        return $self->{'phone_id'};
    }
}

sub PrimaryContactID {
    my $self = shift;
    if (@_) {
        my ($new_val) = @_;
        if ( $new_val ne $self->{'primary_contact_id'} ) {
            $self->{'primary_contact_id'} = $new_val;
            $self->{'dirty'}              = 1;
        }
    } else {
        return $self->{'primary_contact_id'};
    }
}

sub SecondaryContactID {
    my $self = shift;
    if (@_) {
        my ($new_val) = @_;
        if ( $new_val ne $self->{'secondary_contact_id'} ) {
            $self->{'secondary_contact_id'} = $new_val;
            $self->{'dirty'}                = 1;
        }
    } else {
        return $self->{'secondary_contact_id'};
    }
}

# disabled status
sub Disabled {
    my $self = shift;
    if (@_) {
        my ($new_val) = @_;
        if ( $new_val ne $self->{'disabled'} ) {
            $self->{'disabled'} = $new_val;
            $self->{'dirty'}    = 1;
        }
    } else {
        return $self->{'disabled'};
    }
}

# cannot set
sub ClientID {
    my $self = shift;

    if (@_) {
        my ($new_val) = @_;
        $self->{client_id} = $new_val;
    }

    if ( !defined $self->{client_id} ) {
        $self->{client_id} = $ENV{CLIENT_ID};
    }

    return $self->{client_id};
}

# via an access level api.
sub AccessLevel {
    my $self = shift;

    if (@_) {
        my ($new_val) = @_;
        if ( $new_val != $self->AccessLevel ) {
            $self->{access_level}      = $new_val;
            $self->{dirty_user_access} = 1;
        }
    } elsif ( !defined $self->{access_level} ) {
        $self->_loadUserAccess();
    }

    return $self->{access_level};
}

# via an access level api.
sub LabelID {
    my $self = shift;

    if (@_) {
        my ($new_val) = @_;
        $self->{label_id}          = $new_val;
        $self->{dirty_user_access} = 1;
    }

    # if access_level is defined that means we already know if
    # label_id is defined, so only try to load label_id if
    # access_level is not defined.
    elsif ( !defined $self->{label_id} && !defined $self->{access_level} ) {
        $self->_loadUserAccess();
    }

    return $self->{label_id};
}

# cannot set
sub DateCreated {
    my $self = shift;
    return $self->{date_created};
}

# cannot set
sub DateModified {
    my $self = shift;
    return $self->{date_modified};
}

# cannot set
sub Error {
    my $self = shift;
    return $self->{errstr};
}

# -------------------------------
# Public Methods
# -------------------------------
use constant TOKEN_EXPIRE_SECONDS => 604800;    # one week

sub IsValidToken {
    my $self      = shift;
    my $chk_token = shift;

    my $my_token = $self->GetToken( base => 1 );
    if ( $chk_token =~ /^($my_token)(.+)$/ ) {
        my $time_set = $2;
        if ( $time_set + TOKEN_EXPIRE_SECONDS > time() ) {
            return 1;
        } else {
            return undef;
        }
    } else {
        return undef;
    }
}

sub GetToken {
    my $self = shift;
    my %args = @_;

    my $secret_code = "h4ppYc0lOr3dMar81e5";
    my $token = md5_hex( $self->UserID, $self->Email, $secret_code );

    if ( $args{base} ) {
        return $token;
    } else {
        return $token . time();
    }
}

sub SetClientID {
    my $self = shift;

    my $sql  = "SELECT * from user_access WHERE user_id=" . $self->UserID;
    my $sth  = $self->{dbo}->DoCmd($sql);
    my $href = $sth->fetchrow_hashref() if ($sth);

    if ($href) {
        $self->AccessLevel( $href->{access_level} );
        $self->ClientID( $href->{client_id} );
        return $href->{client_id};
    }

    return undef;
}

sub UpdateUserAccess {
    my $self = shift;

    return undef if ( !$self->UserID || !$self->ClientID );

    my $sql;
    $sql = "SELECT * FROM user_access" . " WHERE user_id=" . $self->UserID . " AND client_id=" . $self->ClientID;

    my $sth = $self->{dbo}->DoCmd($sql);
    my $href = $sth->fetchrow_hashref() if ($sth);

    # we need to be careful about setting the label_id field
    my $label_set = "";
    if ( $self->LabelID ) {
        $label_set = ",label_id=" . $self->LabelID;
    } else {
        $label_set = ",label_id=null";
    }

    # add a new row
    if ( !defined $href || $sth->rows == 0 ) {
        $sql =
            "INSERT INTO user_access SET"
          . " user_id="
          . $self->UserID
          . ",client_id="
          . $self->ClientID
          . ",access_level="
          . $self->AccessLevel
          . $label_set
          . ",date_created=now()";
    } else {
        $sql =
            "UPDATE user_access SET"
          . " access_level="
          . $self->AccessLevel
          . $label_set
          . " WHERE user_id="
          . $self->UserID
          . " AND client_id="
          . $self->ClientID;
    }

    return $self->{dbo}->DoCmd($sql);
}

sub CanAccess {
    my $self   = shift;
    my $access = shift;

    my $access_level = $self->AccessLevel || return undef;

    # print STDERR "check access to $access\n";
    return undef if ( !defined $Common::Consts::PERMISSIONS{$access} );
    my @allowed = @{ $Common::Consts::PERMISSIONS{$access} };

    if ( $access_level && scalar( grep /^$access_level$/, @allowed ) > 0 ) {

        # print STDERR "they have access!\n";
        return 1;
    } else {

        # print STDERR "sorry charlie, no access!\n";
        return undef;
    }
}

sub Load {
    my $self    = shift;
    my %args    = @_;
    my $email   = $args{email};
    my $user_id = $args{user_id};

    my $sql;
    if ( defined $user_id && $user_id =~ /^\d+$/ ) {
        if ( $self->ClientID > 0 ) {
            $sql =
                "SELECT u.* FROM "
              . DB_TABLE
              . " u RIGHT JOIN user_access ua ON u.user_id=ua.user_id"
              . " WHERE u.user_id=$user_id"
              . " AND (ua.client_id=0 OR ua.client_id="
              . $self->ClientID . ")";
        } else {
            $sql = "SELECT * FROM " . DB_TABLE . " WHERE user_id=$user_id";
        }
    } elsif ( defined $email && $email =~ /.+\@\w+/ )    # quick and dirty email syntax check
    {
        if ( $self->ClientID > 0 ) {
            $sql =
                "SELECT u.* FROM "
              . DB_TABLE
              . " u RIGHT JOIN user_access ua ON u.user_id=ua.user_id"
              . " WHERE u.email="
              . $self->{dbo}->DBQuote($email)
              . " AND (ua.client_id=0 OR ua.client_id="
              . $self->ClientID . ")";
        } else {
            $sql = "SELECT * FROM " . DB_TABLE . " WHERE email=" . $self->{dbo}->DBQuote($email);
        }
    } else {
        $self->{errstr} = "user_id ($user_id) OR email ($email) not specified or not valid";
        return undef;
    }

    my $sth = $self->{dbo}->DoCmd($sql);
    unless ( defined $sth ) {
        $self->{errstr} = "database error: " . $DBI::errstr;
        return undef;
    }

    my $href = $sth->fetchrow_hashref();
    if ( !defined $href || $sth->rows == 0 ) {
        $self->{errstr} = "no user record for user_id=$user_id OR email=$email";
        return undef;
    }

    $self->_load($href);

    $self->{dirty}             = 0;
    $self->{dirty_user_access} = 0;

    return 1;
}

sub AccessToClient {
    my $self      = shift;
    my $client_id = shift;

    my $sql =
      "SELECT COUNT(*) AS count FROM user_access WHERE (client_id = $client_id OR client_id = 0) AND access_level IN (1,2) AND user_id = "
      . $self->UserID;
    my $sth = $self->{dbo}->DoCmd($sql);

    if ( defined $sth ) {
        my $href = $sth->fetchrow_hashref();
        return 1 if ( $href->{count} > 0 );
    }
    return undef;
}

sub Save {
    my $self = shift;

    if ( $self->{dirty} ) {

        # create sql for saving the user object
        my @set_fields;
        foreach my $attrib (@attributes) {

            # attributes to skip
            next if $attrib =~ /^(date_created|date_modified|user_id)$/;
            my $new_val = $self->{$attrib};
            next if ( !defined $new_val );

            # yes, to set a field to NULL we allow the string "null"
            # to be passed here, in which case we DO NOT want to quote it.
            if ( $self->{$attrib} !~ /^null$/i ) {
                $new_val = $self->{dbo}->DBQuote($new_val);
            }
            push @set_fields, $attrib . "=" . $new_val;
        }

        return undef if ( !@set_fields );

        # update or insert?
        if ( defined $self->{user_id} && $self->{user_id} =~ /^\d+$/ ) {
            my $sql = "UPDATE " . DB_TABLE . " SET " . join( ', ', @set_fields ) . " WHERE user_id=" . $self->{user_id};
            $self->{dbo}->DoCmd($sql);
        } else {
            my $sql = "INSERT INTO " . DB_TABLE . " SET date_created=now(), " . join( ', ', @set_fields );
            $self->{dbo}->DoCmd($sql);
            $self->{user_id} = $self->{dbo}->LastInsertID;
        }

        # NOTE: At this point, we *should* re-load our user object from the database to get the
        # correct values for date_modified (inserts and updates) and date_created (inserts only).
        # But for now, until we realize a need for those values after a Save, we will avoid another
        # database hit and skip grabbing the latest data. If it is decided those values are needed,
        # uncomment the following line:

        # $self->Load(user_id => $self->UserID);

        $self->{dirty} = 0;
    }

    if ( $self->{dirty_user_access} ) {

        # let's also update user_access info
        $self->UpdateUserAccess();
        $self->{dirty_user_access} = 0;
    }

    return 1;
}

sub Delete {
    my $self = shift;

    return undef unless ( defined $self->UserID && defined $self->ClientID );

    my $sql = "SELECT COUNT(*) AS count FROM user_access WHERE user_id = " . $self->UserID;
    my $sth = $self->{dbo}->DoCmd($sql);

    return undef unless ( defined $sth );

    my $href            = $sth->fetchrow_hashref();
    my $only_one_client = 0;
    $only_one_client = 1 if ( $href->{count} == 1 );

    $sql = "DELETE FROM user_access WHERE client_id = " . $self->ClientID . " AND user_id = " . $self->UserID;
    $sth = $self->{dbo}->DoCmd($sql);

    if ( $only_one_client == 1 ) {
        $sql = "DELETE FROM user WHERE user_id = " . $self->UserID;
        $sth = $self->{dbo}->DoCmd($sql);
    }

}

# set the password
sub SetPassword {
    my $self           = shift;
    my $password_plain = shift;

    return undef if ( !defined $password_plain || $password_plain eq '' );

    return undef if ( $self->UserID !~ /^\d+$/ );

    my $sql =
      "UPDATE " . DB_TABLE . " SET password=password(" . $self->{dbo}->DBQuote($password_plain) . ") WHERE user_id=" . $self->UserID;
    my $sth = $self->{dbo}->DoCmd($sql);

    return ( defined $sth ) ? 1 : undef;
}

# validate a password
sub IsPassword {
    my $self           = shift;
    my $password_plain = shift;

    return undef if ( !defined $password_plain || $password_plain eq '' );

    return undef if ( $self->UserID !~ /^\d+$/ );

    my $sql =
        "SELECT * FROM "
      . DB_TABLE
      . " WHERE user_id="
      . $self->UserID
      . " AND password=password("
      . $self->{dbo}->DBQuote($password_plain) . ")";
    print STDERR "sql: $sql\n";
    my $sth = $self->{dbo}->DoCmd($sql);
    return ( $sth->rows() );
}

sub GetAllClients {
    my $self = shift;

    my $sql = "SELECT * FROM user_access WHERE user_id=" . $self->UserID;
    my $sth = $self->{dbo}->DoCmd($sql);

    return undef if ( !$sth );

    my $is_super_user = 0;
    my @client_ids    = ();
    while ( my $href = $sth->fetchrow_hashref() ) {
        if ( $href->{client_id} == 0 ) {
            $is_super_user = 1;
            last;
        }
        push @client_ids, $href->{client_id};
    }

    # the super user only has one entry in this table (client_id=0)
    # so just grab all possible client ids
    if ($is_super_user) {
        $sql = "SELECT client_id FROM client";
        $sth = $self->{dbo}->DoCmd($sql);

        return undef if ( !$sth );

        @client_ids = ();
        while ( my $href = $sth->fetchrow_hashref() ) {
            push @client_ids, $href->{client_id};
        }
    }

    return \@client_ids;
}

sub GetObjectXML {
    my $self     = shift;
    my %args     = @_;
    my $load_sub = $args{load_sub};

    my $xml = $self->SUPER::GetObjectXML(@xml_attributes);

    my $client = new Client::Client( client_id => $self->ClientID );
    $xml->{ClientName}      = $client->ClientName;
    $xml->{ClientNameClean} = $Common::Consts::CLIENT_NAME_CLEAN{ $self->ClientID };
    $xml->{AccessLevelName} = $Common::Consts::PERMISSION_LABELS{ $self->AccessLevel };

    # convert system dates to user friendly dates
    $xml->{DateCreated} =~ s/^(\d\d\d\d)-(\d\d)-(\d\d).+$/$2\/$3\/$1/;
    $xml->{DateModified} =~ s/^(\d\d\d\d)-(\d\d)-(\d\d).+$/$2\/$3\/$1/;

    return $xml;
}

# -------------------------------
# Private Methods
# -------------------------------
sub _load {
    my $self = shift;
    my $href = shift;

    map { $self->{$_} = $href->{$_} } @attributes;
}

sub _loadUserAccess {
    my $self = shift;

    return undef if ( !$self->UserID );

    my $sql = "SELECT * FROM user_access WHERE user_id=" . $self->UserID . " AND (client_id=0 OR client_id=" . $self->ClientID . ")";

    my $sth = $self->{dbo}->DoCmd($sql);
    if ( !defined $sth ) {
        $self->{errstr} = "database error: " . $DBI::errstr;
        return undef;
    }

    my $href = $sth->fetchrow_hashref();
    if ( defined $href && $href->{access_level} ) {
        $self->{access_level} = $href->{access_level};
        $self->{label_id}     = $href->{label_id};
    } else {
        $self->{errstr} = "no user access for user_id=" . $self->UserID . " AND client_id=" . $self->ClientID;
        return undef;
    }
}

#
#
# ---------------------------------------------
# End package User::User
# ---------------------------------------------

# ---------------------------------------------
# Start package User::Users
# ---------------------------------------------
#
#
package User::Users;

# use constant DB_TABLE => "user";
use User::User;

use lib '/app/tools/data_classes/lib';
use Items;
use base 'Items';

# --------------------------------
# Constructor
# --------------------------------
sub new {
    my $class = shift;
    my %args  = @_;

    my $self = $class->SUPER::new(@_);
    $self->{dbo} = new Common::RSDB( client_id => 0 );

    return $self;
}

sub SelectWhereEmailLike {
    my $self   = shift;
    my $lookup = shift;

    my $sql =
        "SELECT u.* FROM "
      . User::User::DB_TABLE
      . " u RIGHT JOIN user_access ua on u.user_id=ua.user_id"
      . " WHERE (ua.client_id=0 OR ua.client_id="
      . $ENV{CLIENT_ID}
      . ") AND email LIKE '%"
      . $lookup . "%'";

    return $self->getByQuery($sql);
}

sub GetAllPagedBy {
    my $self             = shift;
    my %args             = @_;
    my $page             = $args{page} || 1;
    my $num              = $args{num} || 20;
    my $total_pages      = $args{total_pages};
    my $total_results    = $args{total_results};
    my $min_access_level = $args{min_access_level} | 0;
    my $sort             = $args{sort}
      || "ua.access_level = 5, ua.access_level = 6, ua.access_level = 4, ua.access_level = 3, ua.access_level = 2, ua.access_level = 1, u.first_name, u.last_name, u.email";
    my $offset = ( $page - 1 ) * $num;

    if ( ref($total_pages) eq 'SCALAR' ) {
        my $sql_count =
            "SELECT count(*) as total FROM "
          . User::User::DB_TABLE
          . " u RIGHT JOIN user_access ua on u.user_id=ua.user_id"
          . " WHERE (ua.client_id=0 OR ua.client_id="
          . $ENV{CLIENT_ID}
          . ") AND ua.access_level >= $min_access_level";

        my $sth = $self->{dbo}->DoCmd($sql_count);
        my $href = $sth->fetchrow_hashref() if ($sth);
        ## implementing a ceil w/o POSIX
        $$total_pages =
          ( $href->{total} > 0 ) ? int( $href->{total} / $num ) + ( ( $href->{total} / $num != int( $href->{total} / $num ) ) ? 1 : 0 ) : 0;
        $$total_results = $href->{total};
    }

    my $sql =
        "SELECT * FROM "
      . User::User::DB_TABLE
      . " u RIGHT JOIN user_access ua on u.user_id=ua.user_id"
      . " WHERE (ua.client_id=0 OR ua.client_id="
      . $ENV{CLIENT_ID}
      . ") AND ua.access_level >= $min_access_level"
      . " ORDER BY $sort"
      . " LIMIT $num"
      . " OFFSET $offset";

    return $self->getByQuery($sql);
}

sub GetByAccessLevel {
    my $self        = shift;
    my %args        = @_;
    my $levels_aref = $args{value} || return undef;

    my $sql =
        "SELECT u.* FROM user_access ua RIGHT JOIN "
      . User::User::DB_TABLE
      . " u on ua.user_id=u.user_id"
      . " WHERE (ua.client_id=0 OR ua.client_id="
      . $ENV{CLIENT_ID}
      . ") AND ua.access_level IN ("
      . join( ',', @$levels_aref ) . ")";

    return $self->getByQuery($sql);
}

sub GetBySearch {
    my $self             = shift;
    my %args             = @_;
    my $search           = $args{search} || return undef;
    my $page             = $args{page} || 1;
    my $num              = $args{num} || 20;
    my $total_pages      = $args{total_pages};
    my $total_results    = $args{total_results};
    my $min_access_level = $args{min_access_level} | 0;
    my $sort             = $args{sort}
      || "ua.access_level = 5, ua.access_level = 6, ua.access_level = 4, ua.access_level = 3, ua.access_level = 2, ua.access_level = 1, u.first_name, u.last_name, u.email";
    my $offset = ( $page - 1 ) * $num;

    ## make search term LIKE-friendly
    $search = "%" . $search . "%";

    if ( ref($total_pages) eq 'SCALAR' ) {
        my $sql_count =
            "SELECT count(*) as total FROM "
          . User::User::DB_TABLE
          . " u RIGHT JOIN user_access ua on u.user_id=ua.user_id"
          . " WHERE (u.email LIKE ? OR u.first_name LIKE ? OR u.last_name LIKE ?)"
          . " AND (ua.client_id=0 OR ua.client_id="
          . $ENV{CLIENT_ID}
          . ") AND ua.access_level >= $min_access_level";

        my $sth = $self->{dbo}->DoCmdParams( $sql_count, $search, $search, $search );
        my $href = $sth->fetchrow_hashref() if ($sth);
        ## implementing a ceil w/o POSIX
        $$total_pages =
          ( $href->{total} > 0 ) ? int( $href->{total} / $num ) + ( ( $href->{total} / $num != int( $href->{total} / $num ) ) ? 1 : 0 ) : 0;
        $$total_results = $href->{total};
    }

    my $sql =
        "SELECT * FROM "
      . User::User::DB_TABLE
      . " u RIGHT JOIN user_access ua on u.user_id=ua.user_id"
      . " WHERE (u.email LIKE ? OR u.first_name LIKE ? OR u.last_name LIKE ?)"
      . " AND (ua.client_id=0 OR ua.client_id="
      . $ENV{CLIENT_ID}
      . ") AND ua.access_level >= $min_access_level"
      . " ORDER BY $sort"
      . " LIMIT $num"
      . " OFFSET $offset";

    return $self->getByQueryParams( $sql, $search, $search, $search );
}

sub GetNext {
    my $self = shift;

    my $href = $self->{sth}->fetchrow_hashref();
    return undef if ( !defined $href || $href->{user_id} !~ /^\d+$/ );

    my $tmp = new User::User( user_id => $href->{user_id} );
    return undef if ( !defined $tmp || $tmp->UserID !~ /^\d+$/ );

    return $tmp;
}

###
1;    # Play nicely.
###
