package Cpanel::API::Sitejet;

# cpanel - Cpanel/API/Sitejet.pm                 Copyright 2023 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 cPstrict;
use Cpanel::AdminBin::Call     ();
use Cpanel::Autodie            ();
use Cpanel::Exception          ();
use Cpanel::Locale             ();
use Cpanel::Server::Type       ();
use Cpanel::Sitejet::Connector ();

=encoding utf-8

=head1 NAME

Cpanel::API::Sitejet - wrapper to SiteJet api calls.

=head1 DESCRIPTION

A wrapper to call Sitejet REST API calls

=cut

my $non_mutating = { allow_demo => 1 };
my $mutating     = {};

our %API = (
    create_account                  => $non_mutating,
    get_templates                   => $non_mutating,
    set_template                    => $non_mutating,
    get_api_token                   => $non_mutating,
    add_api_token                   => $non_mutating,
    create_website                  => $non_mutating,
    publish                         => $non_mutating,
    get_user_site_metadata          => $non_mutating,
    get_all_sites_metadata          => $non_mutating,
    get_sso_link                    => $non_mutating,
    get_sitebuilder_domain_statuses => $non_mutating,
    disk_quota_check                => $non_mutating,
    get_all_user_sitejet_info       => $non_mutating,
);

=head2 create_account()

Create a sitejet account associated with the current logged in user.

See L<https://api.docs.cpanel.net/openapi/cpanel/operation/create_sitejet_account/> for more details.

=cut

sub create_account ( $args, $result ) {
    my $connector = Cpanel::Sitejet::Connector->new();
    my $res       = $connector->createAccount();
    Cpanel::AdminBin::Call::call( 'Cpanel', 'sitejet', 'ADD_API_TOKEN', $res->{key} );
    $result->data($res);

    return 1;
}

=head2 get_templates()

Get the list of templates available for Sitejet.

See L<https://api.docs.cpanel.net/openapi/cpanel/operation/get_sitejet_templates/> for more details.

=cut

sub get_templates ( $args, $result ) {
    my $connector = Cpanel::Sitejet::Connector->new();
    my $templates = $connector->cp_get_templates() or die Cpanel::Exception::create( 'InvalidParameter', 'Unable to access Sitejet templates. Try again later.' );
    $result->data($templates);

    return 1;
}

=head2 set_template(domain => ..., template_id => ...)

Set the Sitejet template for a given domain.

See L<https://api.docs.cpanel.net/openapi/cpanel/operation/set_sitejet_template/> for more details.

=cut

sub set_template ( $args, $result ) {
    my $domain     = $args->get_length_required('domain');
    my $templateId = $args->get_length_required('templateId');

    _validate_user_domain($domain);
    my $connector = Cpanel::Sitejet::Connector->new();

    my $res = $connector->selectTemplateForWebsite( $domain, $templateId ) or die Cpanel::Exception::create( 'InvalidParameter', 'Unable to set Sitejet template for “[_1]”.', [$domain] );
    $result->data($res);

    return 1;
}

=head2 get_sso_link(domain => ..., referrer => ...)

Fetch the sso link for the specific domain.

See L<https://api.docs.cpanel.net/openapi/cpanel/operation/get_sitejet_sso_link/> for more details.

=cut

sub get_sso_link ( $args, $result ) {
    my $domain   = $args->get_length_required('domain');
    my $referrer = $args->get_length_required('referrer');
    require Cpanel::Locale::Utils::User;
    my $locale = Cpanel::Locale::Utils::User::get_user_locale($Cpanel::user);

    _validate_user_domain($domain);
    my $connector = Cpanel::Sitejet::Connector->new();

    my $sso = $connector->getSSOLinkForWebsite( $domain, $referrer, $locale ) or die Cpanel::Exception::create( 'InvalidParameter', 'Failed to get SSO link for domain: “[_1]”.', [$domain] );
    $result->data($sso);

    return 1;
}

=head2 get_preview_url(websiteId => ...)

Retrieve the website preview url for the website identified by C<websiteId>.

See L<https://api.docs.cpanel.net/openapi/cpanel/operation/get_sitejet_preview_url/> for more details.

=cut

sub get_preview_url ( $args, $result ) {
    my $websiteId = $args->get_length_required('websiteId');

    my $connector = Cpanel::Sitejet::Connector->new();
    my $url       = $connector->cp_getPreviewUrl($websiteId);
    $result->data($url);

    return 1;
}

=head2 get_api_token()

Get the Sitejet API access token for the current cPanel user.

See L<https://api.docs.cpanel.net/openapi/cpanel/operation/get_sitejet_api_token/> for more details.

=cut

sub get_api_token ( $args, $result ) {
    my $api_token = Cpanel::Sitejet::Connector::loadApiKey();
    $result->data($api_token);
    return 1;
}

=head2 add_api_token(api_token => ...)

Add a Sitejet api access token to the current cPanel user.

See L<https://api.docs.cpanel.net/openapi/cpanel/operation/add_sitejet_api_token/> for more details.

=cut

sub add_api_token ( $args, $result ) {
    my $api_token = $args->get_length_required('api_token');

    # XXX do we need to validate the api_token??
    Cpanel::AdminBin::Call::call( 'Cpanel', 'sitejet', 'ADD_API_TOKEN', $api_token );

    return 1;

}

=head2 create_website(...)

Create a website with Sitejet.

See L<https://api.docs.cpanel.net/openapi/cpanel/operation/create_sitejet_website/> for more details.

=cut

sub create_website ( $args, $result ) {
    my %website_info = (
        domain    => $args->get_length_required('domain'),
        company   => $args->get_length_required('company'),
        title     => $args->get('title'),
        firstname => $args->get('firstname'),
        lastname  => $args->get('lastname'),
        street    => $args->get('street'),
        zip       => $args->get('zip'),
        city      => $args->get('city'),
        country   => $args->get('country'),
        language  => $args->get('language'),
        email     => $args->get('email'),
        phone     => $args->get('phone'),
        note      => $args->get('note'),
        metadata  => $args->get('metadata'),
        assignTo  => $args->get('assignTo'),
        language  => $args->get('language') // 'en',

        # hardcoded as does Plesk
        fullcms => 1,

        cpanelDomainGUID => $args->get_length_required('domain'),
    );

    _validate_user_domain( $website_info{domain} );
    my $connector = Cpanel::Sitejet::Connector->new();

    my $website_id = $connector->createWebsite( \%website_info );
    $result->data($website_id);

    return 1;
}

=head2 get_user_site_metadata(domain => ...)

Fetch the metadata for the site hosted at the C<domain>

See L<https://api.docs.cpanel.net/openapi/cpanel/operation/get_user_sitejet_site_metadata/> for more details.

=cut

sub get_user_site_metadata ( $args, $result ) {
    my $domain = $args->get_length_required('domain');

    _validate_user_domain($domain);
    my $connector = Cpanel::Sitejet::Connector->new();

    my $ret_hr = $connector->cp_load_sitejet_metadata($domain);
    $result->data($ret_hr);

    return 1;
}

=head2 get_all_sites_metadata(domain => ...)

Fetch the metadata for all the sites for the current cPanel user.

See L<https://api.docs.cpanel.net/openapi/cpanel/operation/get_all_sitejet_sites_metadata/> for more details.

=cut

sub get_all_sites_metadata ( $args, $result ) {
    my $connector = Cpanel::Sitejet::Connector->new();
    my $ret       = $connector->cp_load_all_sitejet_domains_metadata();
    $result->data($ret);

    return $ret;
}

=head2 publish(domain => ...)

Publish a site built with Sitejet to the docroot directory for the selected domain.

See L<https://api.docs.cpanel.net/openapi/cpanel/operation/publish_sitejet/> for more details.

=cut

sub publish ( $args, $result ) {
    my $domain = $args->get('domain');
    _validate_user_domain($domain);
    require DateTime;
    my $log_file = "$Cpanel::homedir/logs/publish_" . DateTime->now->datetime . ".log";
    my ( $log, $log_entry ) = _create_logging_stream_dir('sitejet');
    Cpanel::Autodie::symlink( $log_file, $log );

    require Cpanel::Daemonizer::Tiny;
    my $pid = Cpanel::Daemonizer::Tiny::run_as_daemon(
        sub {
            Cpanel::initcp($Cpanel::user);
            require Cpanel::Sitejet::Publish;
            eval { Cpanel::Sitejet::Publish::publish( $domain, $log_file ); };

            if ( my $exception = $@ ) {
                local $Cpanel::Plugins::Log::_DIR = "$Cpanel::homedir/logs/sitejet";
                Cpanel::Plugins::Log::set_metadata( "${log_entry}", CHILD_ERROR => 1 );
            }
            else {
                local $Cpanel::Plugins::Log::_DIR = "$Cpanel::homedir/logs/sitejet";
                Cpanel::Plugins::Log::set_metadata( "${log_entry}", CHILD_ERROR => 0 );
            }
        }
    );

    $result->data( { 'pid' => $pid, 'log_entry' => $log_entry, 'user' => $Cpanel::user } );
    return 1;
}

sub _validate_user_domain ($domain) {
    require Cpanel::DomainLookup::DocRoot;
    my @user_domains = keys %{ Cpanel::DomainLookup::DocRoot::getdocroots($Cpanel::user) };

    if ( !grep { /\Q$domain\E/ } @user_domains ) {
        die Cpanel::Exception::create( 'InvalidParameter', 'The “[_1]” domain is not owned by this account.', [$domain] );
    }

    return 1;
}

sub _create_logging_stream_dir ($name) {
    require Cpanel::ProcessLog::WithChildError;
    require Cpanel::Plugins::Log;

    # overriding the default plugins log directory
    local $Cpanel::Plugins::Log::_DIR = "$Cpanel::homedir/logs/sitejet";
    my $log_entry = Cpanel::Plugins::Log::create_new( $name, 'CHILD_ERROR' => '?' );
    my $log       = "$Cpanel::Plugins::Log::_DIR/$log_entry/txt";
    Cpanel::Autodie::unlink_if_exists($log);
    return ( $log, $log_entry );
}

=head2 get_sitebuilder_domain_statuses(domain => ...)

Publish a site built with Sitejet to the docroot directory for the selected domain.

See L<https://api.docs.cpanel.net/openapi/cpanel/operation/get_sitebuilder_domain_statuses/> for more details.

=cut

sub get_sitebuilder_domain_statuses ( $args, $result ) {
    my $domain = $args->get('domain');
    _validate_user_domain($domain) if $domain;
    my $connector = Cpanel::Sitejet::Connector->new();
    my $ret       = $connector->cp_list_all_domain_sitebuilders($domain);
    $result->data($ret);

    return $ret;
}

=head2 disk_quota_check()

This function checks if a user has sufficient free space to perform
the back up of the document_root.

See L<https://api.docs.cpanel.net/openapi/cpanel/operation/sitejet_disk_quota_check/> for more details.

=head3 ARGUMENTS

=over

=item domain - string
Optional. The name of the domain.
If no arguments are provided, it performs the check on all user owned domains.

=back

=head3 RETURNS

1 if there is free space available or if an user has `unlimited` quota.
0 if there is not enough free space available.

=cut

sub disk_quota_check ( $args, $result ) {
    my $domain = $args->get('domain');
    _validate_user_domain($domain) if $domain;
    require Cpanel::Sitejet::Publish;
    my $ret = Cpanel::AdminBin::Call::call( 'Cpanel', 'sitejet', 'DISK_QUOTA_CHECK', $domain );
    $result->data($ret);
    return $ret;
}

=head2 get_all_user_sitejet_info()

This function provides the consolidated view of all sitejet related
information for a single domain or all user owned domains. This includes
sitejet metadata, integration status with sitejet and disk quota check.

See L<https://api.docs.cpanel.net/openapi/cpanel/operation/get_all_user_sitejet_info/> for more details.

=head3 ARGUMENTS

=over

=item domain - string
Optional. The name of the domain.
If no arguments are provided, it performs the check on all user owned domains.

=back

=head3 RETURNS
    Reference to array of anonymous hashes containing information about
    each domain website or individual domain website which looks something like this:
    [
        {
            'metadata' => {
                'document_root'    => '/home/user/public_html',
                'company'          => 'userdomain.tld',
                'websiteId'        => 399661,
                'publish_status'   => 1,
                'templateId'       => '371888',
                'fullcms'          => 1,
                'cpanelDomainGUID' => 'userdomain.tld',
                'language'         => 'en'
            },
            'status' => {
                'has_sitejet_published'  => 1,
                'is_sitejet'             => 1,
                'has_sitejet_website'    => 1,
                'has_sitejet_templateId' => 1
            },
            'quota' => {
                'required_space'    => 22,
                'can_backup'        => 1,
                'available_space'   => 281,
                'is_docroot_empty'  => 0
            },
            'domain' => 'userdomain.tld'
        }
    ]

=cut

sub get_all_user_sitejet_info ( $args, $result ) {
    my $domain = $args->get('domain');
    _validate_user_domain($domain) if $domain;

    my $metadata  = {};
    my $connector = Cpanel::Sitejet::Connector->new();
    if ($domain) {
        $metadata->{$domain} = $connector->cp_load_sitejet_metadata($domain);
    }
    else {
        $metadata = $connector->cp_load_all_sitejet_domains_metadata;
    }
    my $status         = $connector->cp_list_all_domain_sitebuilders($domain);
    my $disk_quota     = Cpanel::AdminBin::Call::call( 'Cpanel', 'sitejet', 'DISK_QUOTA_CHECK', $domain );
    my @final_data     = ();
    my @keys_to_remove = ( 'zz_light_bg', 'zz_dark_bg', 'domain' );
    for ( my $i = 0; $i < @$status; $i++ ) {
        my %build_data;
        my $document_root;
        my $domain = $status->[$i]->{'domain'};
        map { delete $metadata->{$domain}->{$_} } @keys_to_remove;
        $build_data{'domain'}   = $domain;
        $build_data{'status'}   = $status->[$i]->{'info'};
        $build_data{'metadata'} = $metadata->{$domain};
        if ( !exists( $metadata->{$domain}->{'document_root'} ) ) {
            require Cpanel::Sitejet::Publish;
            $document_root = Cpanel::Sitejet::Publish::get_document_root($domain);
        }
        else {
            $document_root = $metadata->{$domain}->{'document_root'};
        }
        $build_data{'quota'} = $disk_quota->{$domain};
        push @final_data, \%build_data;
    }
    $result->data( \@final_data );

    return 1;

}

1;
