<?php

defined('BASEPATH') or exit('No direct script access allowed');

@ini_set('memory_limit', '512M');
@ini_set('max_execution_time', 360);

class App_bulk_pdf_export
{
    /**
     * The export config
     *
     * @var array
     */
    protected $config = [];

    /**
     * Codeigniter instance
     * @var object
     */
    protected $ci;

    /**
     * This property is used to store the PDF
     * @var object
     */
    public $pdf_zip;

    /**
     * Can view based on the $type property
     * @var boolean
     */
    protected $can_view;

    /**
     * Export type
     * Possible values: invoices, estimates, credit_notes, payments, proposal
     * @var string
     */
    protected $type;

    /**
     * Filter period from
     * @var string
     */
    protected $date_from;

    /**
     * Filter period to
     * @var string
     */
    protected $date_to;

    /**
     * Payments export payment mode
     * @var string
     */
    protected $payment_mode;

    /**
     * Status for estimates, invoices, credit notes, proposals
     * @var mixed
     */
    protected $status;

    /**
     * Required status parameter
     * @var array
     */
    protected $status_param_required = ['invoices', 'estimates', 'credit_notes', 'proposals'];

    /**
     * PDF tag
     * @var string
     */
    protected $pdf_tag = '';

    /**
     * Zip directory
     * @var string
     */
    protected $zip_dir;

    /**
     * Unique years for the data that is exporting
     * Used to create the folders
     * @var array
     */
    protected $years = [];

    /**
     * Client ID if exporting for specific client
     * @var mixed
     */
    protected $client_id = null;

    /**
     * Client ID Column
     * @var string
     */
    protected $client_id_column = 'clientid';

    /**
     * Add the main folder contents in folder
     * e.q. customer-name/YEAR/INVOICE.pdf
     * @var string
     */
    protected $in_folder = null;

    /**
     * Redirect user to specific url after error
     * This parameter is required
     * @var string
     */
    protected $redirect_on_error = '';

    public function __construct($config)
    {
        $this->type = $config['export_type'];

        $this->config = $config;

        if (!isset($config['redirect_on_error'])) {
            show_error('You must set parameter "redirect_on_error"');
        } else {
            $this->redirect_on_error = $config['redirect_on_error'];
        }

        if (in_array($this->type, $this->status_param_required)
            && (!isset($config['status']) || isset($config['status']) && empty($config['status']))) {
            show_error('You must set "status" config if you are exporting data for: ' . implode(', ', $this->status_param_required));
        }

        if (isset($config['status'])) {
            $this->status = $config['status'];
        }

        if (isset($config['client_id'])) {
            $this->set_client_id($config['client_id']);
        }

        if (isset($config['date_from']) && isset($config['date_to'])) {
            $this->date_from = to_sql_date($config['date_from']);
            $this->date_to   = to_sql_date($config['date_to']);
        }

        if (isset($config['payment_mode'])) {
            $this->payment_mode = $config['payment_mode'];
        }

        if (isset($config['tag']) && !empty($config['tag'])) {
            $this->pdf_tag = $config['tag'];
        }

        $this->ci = &get_instance();
        $this->ci->load->library('zip');
        $this->can_view = has_permission($this->type, '', 'view');

        if (!is_really_writable(TEMP_FOLDER)) {
            show_error(TEMP_FOLDER . ' folder is not writable. You need to change the permissions to 0755');
        }

        $this->dir = TEMP_FOLDER . $this->type;

        if (is_dir($this->dir)) {
            $this->clear($this->dir);
        }

        mkdir($this->dir, 0755);
        register_shutdown_function([$this, 'clear'], $this->dir);
    }

    /**
     * The main function for exporting
     * @return mixed
     */
    public function export()
    {
        if (method_exists($this, $this->type)) {
            $data = $this->{$this->type}();
        } else {
            if (!hooks()->has_filter('bulk_pdf_export_class')) {
                show_error('No export type selected!');
            } else {
                $class = hooks()->apply_filters('bulk_pdf_export_class', null, $this->type);

                if (!$class) {
                    show_error('No export type found!');
                }

                $class = new $class($this->config);
                $class->export();
            }
        }

        $this->zip();
    }

    /**
     * Create payment export
     * @return object
     */
    protected function payments()
    {
        $this->ci->db->select('' . db_prefix() . 'invoicepaymentrecords.id as paymentid');
        $this->ci->db->from(db_prefix() . 'invoicepaymentrecords');
        $this->ci->db->join(db_prefix() . 'invoices', '' . db_prefix() . 'invoices.id = ' . db_prefix() . 'invoicepaymentrecords.invoiceid', 'left');
        $this->ci->db->join(db_prefix() . 'clients', '' . db_prefix() . 'clients.userid = ' . db_prefix() . 'invoices.clientid', 'left');

        if (!$this->can_view) {
            $whereUser = '';
            $whereUser .= '(invoiceid IN (SELECT id FROM ' . db_prefix() . 'invoices WHERE addedfrom=' . get_staff_user_id() . ')';
            if (get_option('allow_staff_view_invoices_assigned') == 1) {
                $whereUser .= ' OR invoiceid IN (SELECT id FROM ' . db_prefix() . 'invoices WHERE sale_agent=' . get_staff_user_id() . ')';
            }
            $whereUser .= ')';
            $this->ci->db->where($whereUser);
        }
        if ($this->payment_mode) {
            $this->ci->db->where('paymentmode', $this->payment_mode);
        }

        $this->ci->db->order_by($this->get_date_column(), 'desc');

        $data = $this->finalize();

        $this->ci->load->model('payments_model');
        $this->ci->load->model('invoices_model');
        foreach ($data as $payment) {
            $payment_data = $this->ci->payments_model->get($payment['paymentid']);

            $payment_data->invoice_data = $this->ci->invoices_model->get($payment_data->invoiceid);

            $file_name = strtoupper(_l('payment'));
            $file_name .= '-' . strtoupper($payment_data->paymentid) . '.pdf';

            $pdf = payment_pdf($payment_data, $this->pdf_tag);
            $this->save_to_dir($payment_data, $pdf, $file_name);
        }

        return $this;
    }

    /**
     * Create payment export
     * @return object
     */
    protected function expenses()
    {
        $this->ci->load->model('payment_modes_model');
        $payment_gateways = $this->ci->payment_modes_model->get_payment_gateways(true);

        $this->ci->db->select('*,' . db_prefix() . 'expenses.id as id,' . db_prefix() . 'expenses_categories.name as category_name,' . db_prefix() . 'payment_modes.name as payment_mode_name,' . db_prefix() . 'taxes.name as tax_name, ' . db_prefix() . 'taxes.taxrate as taxrate,' . db_prefix() . 'taxes_2.name as tax_name2, ' . db_prefix() . 'taxes_2.taxrate as taxrate2, ' . db_prefix() . 'expenses.id as expenseid,' . db_prefix() . 'expenses.addedfrom as addedfrom');
        $this->ci->db->from('expenses');
        $this->ci->db->join(db_prefix() . 'clients', '' . db_prefix() . 'clients.userid = ' . db_prefix() . 'expenses.clientid', 'left');
        $this->ci->db->join(db_prefix() . 'payment_modes', '' . db_prefix() . 'payment_modes.id = ' . db_prefix() . 'expenses.paymentmode', 'left');
        $this->ci->db->join(db_prefix() . 'taxes', '' . db_prefix() . 'taxes.id = ' . db_prefix() . 'expenses.tax', 'left');
        $this->ci->db->join(db_prefix() . 'taxes as ' . db_prefix() . 'taxes_2', '' . db_prefix() . 'taxes_2.id = ' . db_prefix() . 'expenses.tax2', 'left');

        $this->ci->db->join(db_prefix() . 'expenses_categories', '' . db_prefix() . 'expenses_categories.id = ' . db_prefix() . 'expenses.category');

        if (!$this->can_view) {
            $this->ci->db->where('addedfrom', get_staff_user_id());
        }

        if ($this->payment_mode) {
            $this->ci->db->where('paymentmode', $this->payment_mode);
        }

        $data = $this->finalize();

        $expensesIds = array_column($data, 'expenseid');

        $this->ci->db->select('file_name, filetype, staffid, rel_id')
        ->where_in('rel_id', $expensesIds)
        ->where('rel_type', 'expense');

        $files = $this->ci->db->get('files')->result_array();

        $projectIds = array_filter(array_column($data, 'project_id'));
        $projects   = [];

        if (count($projectIds) > 0) {
            $this->ci->db->select('id, name')->where_in('id', $projectIds);
            $projects = $this->ci->db->get('projects')->result_array();
        }

        foreach ($data as $expense) {
            $expense   = array_to_object($expense);
            $fileIndex = array_search($expense->expenseid, array_column($files, 'rel_id'));

            if ($fileIndex !== false) {
                $expense->attachment            = $files[$fileIndex]['file_name'];
                $expense->filetype              = $files[$fileIndex]['filetype'];
                $expense->attachment_added_from = $files[$fileIndex]['staffid'];
            }

            if ($expense->project_id) {
                $expense->project_data = array_to_object(
                    $projects[array_search($expense->project_id, array_column($projects, 'id'))]
                );
            }

            if (is_null($expense->payment_mode_name)) {
                // is online payment mode
                $expense->payment_mode_name = $payment_gateways[
                    array_search($expense->paymentmode, array_column($payment_gateways, 'id'))
                ] ?? null;
            }

            $expense->currency_data = get_currency($expense->currency);
            $slug                   = sanitize_file_name(slug_it($expense->category_name . '_' . _d($expense->date)));
            $pdf                    = app_pdf('expense', LIBSPATH . 'pdf/Expense_pdf', $expense, $this->pdf_tag);

            $dir = $this->save_to_dir($expense, $pdf, $slug . '.pdf', isset($expense->attachment) ? $slug : '');

            if (isset($expense->attachment)) {
                xcopy(
                    get_upload_path_by_type('expense') . $expense->expenseid . '/' . $expense->attachment,
                    $dir . '/' . $expense->attachment
                );
            }
        }

        return $this;
    }

    /**
     * Create estimates export
     * @return object
     */
    protected function estimates()
    {
        $noPermissionQuery = get_estimates_where_sql_for_staff(get_staff_user_id());

        $this->ci->db->select('id');
        $this->ci->db->from(db_prefix() . 'estimates');

        if ($this->status != 'all') {
            $this->ci->db->where('status', $this->status);
        }

        if (!$this->can_view) {
            $this->ci->db->where($noPermissionQuery);
        }

        $this->ci->db->order_by($this->get_date_column(), 'desc');

        $data = $this->finalize();
        $this->ci->load->model('estimates_model');
        foreach ($data as $estimate) {
            $estimate = $this->ci->estimates_model->get($estimate['id']);
            $pdf      = estimate_pdf($estimate, $this->pdf_tag);
            $this->save_to_dir($estimate, $pdf, strtoupper(slug_it(format_estimate_number($estimate->id))) . '.pdf');
        }

        return $this;
    }

    /**
     * Create invoices export
     * @return object
     */
    protected function invoices()
    {
        $noPermissionQuery = get_invoices_where_sql_for_staff(get_staff_user_id());
        $notSentQuery      = 'sent=0 AND status NOT IN(2,5)' . (!$this->can_view ? ' AND (' . $noPermissionQuery . ')' : '');

        $this->ci->db->select('id');
        $this->ci->db->from(db_prefix() . 'invoices');
        if ($this->status != 'all') {
            if (is_numeric($this->status)) {
                $this->ci->db->where('status', $this->status);
            } else {
                $this->ci->db->where($notSentQuery);
            }
        }

        if (!$this->can_view) {
            $this->ci->db->where($noPermissionQuery);
        }

        $this->ci->db->order_by($this->get_date_column(), 'desc');

        $data = $this->finalize();

        $this->ci->load->model('invoices_model');
        foreach ($data as $invoice) {
            $invoice = $this->ci->invoices_model->get($invoice['id']);
            $pdf     = invoice_pdf($invoice, $this->pdf_tag);
            $this->save_to_dir($invoice, $pdf, strtoupper(slug_it(format_invoice_number($invoice->id))) . '.pdf');
        }

        return $this;
    }

    /**
     * Create proposals export
     * @return object
     */
    public function proposals()
    {
        $noPermissionQuery = get_proposals_sql_where_staff(get_staff_user_id());

        $this->ci->db->select('id');
        $this->ci->db->from(db_prefix() . 'proposals');
        if ($this->status != 'all') {
            $this->ci->db->where('status', $this->status);
        }

        if (!$this->can_view) {
            $this->ci->db->where($noPermissionQuery);
        }

        $this->ci->db->order_by($this->get_date_column(), 'desc');

        $data = $this->finalize();

        $this->ci->load->model('proposals_model');
        foreach ($data as $proposal) {
            $proposal = $this->ci->proposals_model->get($proposal['id']);
            $pdf      = proposal_pdf($proposal, $this->pdf_tag);
            $this->save_to_dir($proposal, $pdf, strtoupper(format_proposal_number($proposal->id)) . '.pdf');
        }

        return $this;
    }

    /**
     * Create credit notes export
     * @return object
     */
    public function credit_notes()
    {
        $this->ci->db->select('id');
        $this->ci->db->from(db_prefix() . 'creditnotes');

        if ($this->status != 'all') {
            $this->ci->db->where('status', $this->status);
        }

        if (!$this->can_view) {
            $this->ci->db->where('addedfrom', get_staff_user_id());
        }

        $this->ci->db->order_by($this->get_date_column(), 'desc');

        $data = $this->finalize();
        $this->ci->load->model('credit_notes_model');
        foreach ($data as $credit_note) {
            $credit_note = $this->ci->credit_notes_model->get($credit_note['id']);
            $pdf         = credit_note_pdf($credit_note, $this->pdf_tag);
            $this->save_to_dir($credit_note, $pdf, strtoupper(slug_it(format_credit_note_number($credit_note->id))) . '.pdf');
        }

        return $this;
    }

    /**
     * Sets the client id column
     * @param string $column
     */
    public function set_client_id_column($column)
    {
        $this->client_id_column = $column;

        return $this;
    }

    /**
     * Set client id
     * @param mixed $client_id
     */
    public function set_client_id($client_id)
    {
        $this->client_id = $client_id;

        return $this;
    }

    /**
     * Set export contents in folder
     * @param  string $folder
     * @return object
     */
    public function in_folder($folder)
    {
        $this->in_folder = $folder;

        return $this;
    }

    /**
     * Used to zip the data in the folder
     * @return null
     */
    protected function zip()
    {
        $this->ci->zip->read_dir($this->dir, false);
        $this->ci->zip->download(slug_it(get_option('companyname')) . '-' . $this->type . '.zip');
        $this->ci->zip->clear_data();
    }

    /**
     * Save the PDF to the temporary directory to zip later
     * @param  object $object     the data object, e.q. invoice, estimate
     * @param  mixed $pdf         the actual PDF
     * @param  string file name   for the PDF file
     * @param string $folderName  additional folder for storage
     *
     * @return string
     */
    protected function save_to_dir($object, $pdf, $file_name, $folderName = '')
    {
        $dir = $this->dir . '/';

        if ($this->in_folder) {
            $dir .= $this->in_folder . '/';
        }

        $dateColumn = str_replace('`', '', $this->get_date_column());

        if (strpos($dateColumn, '.') !== false) {
            $dateColumn = strafter($dateColumn, '.');
        }

        if (!empty($object->{$dateColumn})) {
            $dir .= date('Y', strtotime($object->{$dateColumn})) . '/';
        }

        if ($folderName !== '') {
            $dir .= $folderName . '/';

            if (!is_dir($dir)) {
                mkdir($dir, 0755);
            }
        }

        $path = $dir . $file_name;

        $this->pdf_zip = $pdf;
        $this->pdf_zip->Output($path, 'F');

        return $dir;
    }

    /**
     * Set date query for the data that is exported
     */
    protected function set_date_query()
    {
        if ($this->date_from && $this->date_to) {
            $date_field = $this->get_date_column();
            if ($this->date_from == $this->date_to) {
                $this->ci->db->where($date_field, $this->date_from);
            } else {
                $this->ci->db->where($date_field . ' BETWEEN "' . $this->date_from . '" AND "' . $this->date_to . '"');
            }
        }
    }

    /**
     * Finalize all the query and necessary actions, used for common export options
     * @return array
     */
    protected function finalize()
    {
        $this->set_date_query();

        if ($this->client_id) {
            $this->ci->db->where($this->client_id_column, $this->client_id);
        }

        $data          = $this->ci->db->get()->result_array();
        $last_query    = $this->ci->db->last_query();
        $withoutSelect = strafter($last_query, 'FROM');

        $yearSelectQuery = 'SELECT DISTINCT(YEAR(' . $this->get_date_column() . ')) as year FROM' . str_replace('ORDER BY ' . $this->get_date_column() . '', 'ORDER BY year', $withoutSelect);

        $years = $this->ci->db->query($yearSelectQuery)->result_array();

        if (count($data) == 0) {
            set_alert('warning', _l('no_data_found_bulk_pdf_export'));
            redirect($this->redirect_on_error);
        }

        $this->set_years_and_create_directories($years);

        return $data;
    }

    /**
     * Set years property and create years directories
     * @param array $years
     */
    protected function set_years_and_create_directories($years)
    {
        $flat = [];
        $dir  = $this->dir . '/';
        if ($this->in_folder) {
            $dir .= $this->in_folder . '/';
        }

        foreach ($years as $year) {
            if (!is_dir($dir . $year['year'])) {
                mkdir($dir . $year['year'], 0755, true);
            }
            $flat[] = $year['year'];
        }

        $this->years = $flat;
    }

    /**
     * Get the date column for the exported feature
     * @return string
     */
    protected function get_date_column()
    {
        $date_field = '`date`';
        // Column date is ambiguous in payments
        if ($this->type == 'payments') {
            $date_field = '`' . db_prefix() . 'invoicepaymentrecords`.`date`';
        }

        return $date_field;
    }

    /**
     * Clear the temporary folder
     * @param  string $dir directory to clear
     * @return null
     */
    public function clear($dir)
    {
        if ($dir == TEMP_FOLDER) {
            return true;
        }

        if (is_dir($dir)) {
            delete_files($dir);
            delete_dir($dir);
        }
    }
}
