package Cpanel::CCS::Delegates;

# cpanel - Cpanel/CCS/Delegates.pm                  Copyright 2020 cPanel L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited

use strict;
use warnings;

=head1 NAME

Cpanel::CCS::Delegates

=head1 SYNOPSIS

my $delegates_ar = Cpanel::CCS::Delegates::get($dbh);

=head1 DESCRIPTION

Module for getting and modifying delegation status from the
cpanel-ccs-calendarserver postgresql database cluster.

=head1 SUBROUTINES

=head2 get

Accepts a dbh from Cpanel::CCS::DBUtils::get_dbh()
Returns ARRAYREF of HASHREFS containing delegate data.

=cut

sub get {
    my ( $users_hr, $dbh ) = @_;
    my %uuids2users = map { $users_hr->{$_}{'uuid'} => $_ } keys(%$users_hr);
    my $qs          = ( '?,' x ( scalar( keys(%uuids2users) ) - 1 ) ) . '?';
    my $stmt        = "SELECT delegator, delegate, read_write FROM delegates WHERE delegator IN( $qs )";
    my $ret         = $dbh->selectall_arrayref( $stmt, undef, keys(%uuids2users) );
    return [] if ( ref $ret ne 'ARRAY' || !@$ret );

    # Massage $ret so as to translate uuids -> email addresses/cpuser name.
    my @delegates;
    foreach my $row_ar (@$ret) {
        my $row_hr = {
            'delegator' => $row_ar->[0],
            'delegatee' => $row_ar->[1],
            'readonly'  => $row_ar->[2] ? 0 : 1,
        };
        next if !exists( $uuids2users{ $row_hr->{'delegator'} } );
        next if !exists( $uuids2users{ $row_hr->{'delegatee'} } );
        my $delegator = $uuids2users{ $row_hr->{'delegator'} };

        # Now, translate those UUIDs to CCS users
        $row_hr->{'delegator'} = $uuids2users{ $row_hr->{'delegator'} };
        $row_hr->{'delegatee'} = $uuids2users{ $row_hr->{'delegatee'} };

        push @delegates, $row_hr;
    }
    return \@delegates;
}

=head2 manage

Accepts HASHREF of options:
* delegator: STRING  - CCS User you want to delegate calendars from
* delegatee: STRING  - CCS User you want to add delegated calendars to
* readonly:  BOOL    - Whether or not the delegatee can edit the calendars
* action:    STRING  - CREATE, UPDATE or DELETE
* users_hr:  HASHREF - users from Cpanel::CCS::Userdata::get_users
* dbh:       OBJECT  - DBH from Cpanel::CCS::DBUtils::get_dbh
* cpuser:    STRING  - cPanel user to limit the transaction by.

Returns the result of the query which processes the given 'action'.

=cut

# If you don't like positional args, you probably won't like this, as I'm
# effectively treating them as positional despite you passing them in as hash.
# Don't really care how you feel about it. It works, is ultimately less to type
# opposed to having to dereference.
sub manage {
    my ($args_hr) = @_;
    my ( $delegator, $delegatee, $readonly, $action, $users_hr, $dbh, $cpuser ) = map { $args_hr->{$_} } qw{delegator delegatee readonly action users_hr dbh cpuser};
    die "Delegator '$delegator' doesn't exist or doesn't belong to '$cpuser'" if !$users_hr->{$delegator};
    die "Delegatee '$delegatee' doesn't exist or doesn't belong to '$cpuser'" if !$users_hr->{$delegatee};

    my ( $delegator_uuid, $delegatee_uuid ) = ( $users_hr->{$delegator}{'uuid'}, $users_hr->{$delegatee}{'uuid'} );
    die "Couldn't find uuid for delegator '$delegator'!" if !$delegator_uuid;
    die "Couldn't find uuid for delegatee '$delegatee'!" if !$delegatee_uuid;

    my $upsert = "WITH new_values ( delegator, delegate, read_write )
        AS ( VALUES ( ?, ?, CAST( ? AS int ) ) ),
        upsert AS (
            UPDATE delegates
            SET delegator= ?, delegate = ?, read_write = ?
            FROM new_values
            WHERE delegates.delegator = ? AND delegates.delegate = ?
            RETURNING delegates.*
        )
        INSERT INTO delegates ( delegator, delegate, read_write )
        SELECT delegator, delegate, read_write
        FROM new_values
        WHERE NOT EXISTS ( SELECT 1 FROM upsert WHERE upsert.delegator = new_values.delegator AND upsert.delegate = new_values.delegate );";

    my $insert_or_update = [
        $upsert,
        [ $delegator_uuid, $delegatee_uuid, $readonly ? '0' : '1', $delegator_uuid, $delegatee_uuid, $readonly ? '0' : '1', $delegator_uuid, $delegatee_uuid ]
    ];
    my %stmts = (
        'INSERT' => $insert_or_update,
        'UPDATE' => $insert_or_update,
        'DELETE' => [ q<DELETE FROM delegates WHERE delegator = ? AND delegate = ?>, [ $delegator_uuid, $delegatee_uuid ] ],
    );
    die "Invalid action '$action' -- valid types are 'INSERT', 'UPDATE' and 'DROP'." if !grep { $action eq $_ } keys(%stmts);

    my $ret = $dbh->do( $stmts{$action}->[0], undef, @{ $stmts{$action}->[1] } );

    require Cpanel::ServerTasks;
    eval { Cpanel::ServerTasks::queue_task( ['CCSTasks'], "flush_memcached" ); };
    return $ret;
}

1;
