<?php 

/**
 * 
 */
class pm_invoice
{
	
	public $items = array();
	private $refs = "";

	function __construct($user_id = false)
	{
		$this->session = Session::getInstance();
		$this->template = new template;
		$this->user_id = $user_id ? $user_id : $this->session->id;
		$this->user = new user;
		$this->user_meta = $this->user->get_user_meta($this->user_id);
		$this->prefix = "INV";
		$this->invoice = false;
		$this->query = new query;
		$this->success_method = "_pm_default_invoice_success_method";
		$this->error_method = "_pm_default_invoice_error_method";
		$this->due_date = time();
		$this->items = array();
		$this->name = get_fullname($this->user_meta);
		$this->address = $this->user_meta['address'] ?? '';
		$this->email = $this->user_meta['email'] ?? '';
		$this->phone = internationalize_phone_number($this->user_meta['phone'] ?? '', $this->user_meta['country_code'] ?? '');
	}

	public function add_item($amount, $narration, $feeid, $quantity = 1, $ref = false, $success_method = "_pm_default_success_method", $error_method = "_pm_default_error_method", $remove_duplicate = false) {
		$ref = !$ref ? pm_generate_random_reference() : $ref;
		$inv2 = $this->item_in_another_invoice($ref);
		if($inv2) {
			if($remove_duplicate) {
				if($this->invoice && $this->invoice->invoice_number === $inv2->invoice_number) {
					// unset($this->items[$ref]);
				} else {
					// check to see if there is a credit instance for the particular previous invoice
					$invoice_paid = $this->invoice_credit_instances($inv2->invoice_number, ['confirmed', 'approved', 'pending']);
					if($invoice_paid && ($this->total() > 0)) {
						return false;
					}

					// creates an instance for the duplicate invoice
					$instance = new self($this->user_id);
					$instance->set_invoice_by_number($inv2->invoice_number);

					// transfers the item from the duplicate invoice to the new invoice
					if($instance->items[$ref] !== null) {
						$this->items[$ref] = $instance->items[$ref];
						$this->refs .= "[".$ref."]";

					}
					// removes the item from the duplicate invoice
					$instance->remove_item($ref);

					// saves the duplicate invoice
					$instance->save();

					// saves the new invoice
					$this->save();

					// return current items
					return $this->items;
				}
				
			} else {
				$this->invoice = $inv2;
				$inv2_items = $this->unserialize_invoice_items($inv2->items);
				if(is_array($this->items) && is_array($inv2_items)) {
					$this->items = array_merge($this->items, $inv2_items);
				} else $this->items = $inv2_items;
				$this->refs = $this->refs.$inv2->refs;
				$this->check_for_discounts();
				return $this->items;
			}
		}

		if($this->has_credit_instance($ref, true)) {
			$remark = 'paid';
		} else $remark = "unpaid";
		
		$success_method = function_exists($success_method) ? $success_method : "_pm_default_success_method";
		$error_method = function_exists($error_method) ? $error_method : "_pm_default_error_method";
		
		$this->items[$ref] = [
			'amount' => $amount,
			'narration' => $narration,
			'feeid' => $feeid,
			'ref' => $ref,
			'quantity' => $quantity,
			'remark' => $remark,
			'success_method' => $success_method,
			'error_method' => $error_method
		];
		$this->refs = str_replace("[".$ref."]", "", $this->refs);
		$this->refs .= "[".$ref."]";

		return $this->items;
	}

	public function receipt_template($items, $placeholders = array()) {
		$template = new template();
		$placeholders['thead'] = $placeholders['thead'] ?? array();
		$default_head = [
			"description_label" => "Description",
			"quantity_label" => "Qty",
			"amount_label" => "Amount",
			"subtotal_label" => "Subtotal",
			"bg_color" => get_option("primary_color"),
			"color" => "#fff"
		];
		$placeholders['thead'] = array_merge($default_head, $placeholders['thead']);
		$thead = $this->template->get_template("invoice-thead", $placeholders['thead']);
		$total = 0;
		$tbody = "";
		if(_count($items) && is_array($items)) {
			$i = 1;
			foreach ($items as $ref => $item) {
				$placeholders['tbody'] = $placeholders['tbody'] ?? array();

				if(current_user_can("update_software")) {
					$placeholders['tbody']['item_success'] = "Success: ".$item['success_method'];
					$placeholders['tbody']['item_error'] = " | Error: ".$item['error_method'];
				} else {
					$placeholders['tbody']['item_success'] = "";
					$placeholders['tbody']['item_error'] = "";
				}
				$placeholders['tbody']['item_description'] = ucwords($item['narration']);
				$placeholders['tbody']['item_quantity'] = $item['quantity'];
				$placeholders['tbody']['item_unit_cost'] = toMoney($item['amount']);
				$placeholders['tbody']['item_subtotal'] = toMoney(($item['amount'] * $item['quantity']));
				$tbody .= $this->template->get_template("invoice-tbody", $placeholders['tbody']);
				$i++; $total += ($item['amount'] * $item['quantity']);
			}
		}
		$placeholders['tfoot'] = $placeholders['tfoot'] ?? array();
		$placeholders['tfoot']['invoice_total'] = toMoney($total);
		$tfoot = $this->template->get_template("invoice-tfoot", $placeholders['tfoot']);
		return $this->template->get_template("invoice-items", [
			'invoice_thead' => $thead,
			'invoice_tbody' => $tbody,
			'invoice_tfoot' => $tfoot
		]);
	}

	public function payment_template($credits, $placeholders = array()) {
		if(!_count($credits)) return "";
		$placeholders['thead'] = $placeholders['thead'] ?? array();
		$default_head = [
			"description_label" => "Date",
			"quantity_label" => "Payment Method",
			"amount_label" => "Reference",
			"subtotal_label" => "Amount",
			"bg_color" => "#ebeceb",
			"color" => get_option("primary_color")
		];
		$placeholders['thead'] = array_merge($default_head, $placeholders['thead']);
		$thead = $this->template->get_template("invoice-thead", $placeholders['thead']);
		$total = 0;
		$tbody = "";
		if(_count($credits) && is_array($credits)) {
			$i = 1;
			foreach ($credits as $meta) {
				$placeholders['tbody'] = $placeholders['tbody'] ?? array();
				$placeholders['tbody']['item_description'] = date(_DATE_FORMAT_, $meta['posted_on']);
				$placeholders['tbody']['item_quantity'] = $meta['mode'];
				$placeholders['tbody']['item_unit_cost'] = $meta['in_ref'];
				$placeholders['tbody']['item_subtotal'] = toMoney($meta['amount']);
				$tbody .= $this->template->get_template("invoice-tbody", $placeholders['tbody']);
				$i++; $total += is_numeric($meta['amount']) ? $meta['amount'] : 0;;
			}
		}
		$placeholders['tfoot'] = $placeholders['tfoot'] ?? array();
		$placeholders['tfoot']['invoice_total'] = toMoney($total);
		$tfoot = $this->template->get_template("invoice-tfoot", $placeholders['tfoot']);
		return $this->template->get_template("payments", [
			'invoice_thead' => $thead,
			'invoice_tbody' => $tbody,
			'invoice_tfoot' => $tfoot
		]);
	}

	public function invoice_template($user_meta = false) {
		$template = new template;
		$post = new post;
		$credits = $post->get_post_by_meta_data(['type' => 'credit', 'in_ref' => $this->invoice->invoice_number, 'status' => ['approved', 'confirmed']]);
		$placeholders = [
			'invoice_items' => $this->receipt_template($this->items),
			'invoice_payments' => $this->payment_template($credits),
			'date_of_invoice' => date("M d, Y", $this->invoice->date_created),
			'invoice_status' => $this->status(),
			'invoice_number' => $this->invoice->invoice_number,
			'invoice_due_date' => date("M d, Y", $this->invoice->due_date)
		];
		
		$placeholders['name_on_invoice'] = ($this->invoice->name == NULL || $this->invoice->name == "") ? $this->name : $this->invoice->name;
		$placeholders['address_on_invoice'] = ($this->invoice->address == NULL || $this->invoice->address == "") ? $this->address : $this->invoice->address;
		$placeholders['phone_on_invoice'] = ($this->invoice->phone == NULL || $this->invoice->phone == "") ? $this->phone : $this->invoice->phone;
		$placeholders['email_on_invoice'] = ($this->invoice->email == NULL || $this->invoice->email == "") ? $this->email : $this->invoice->email;
		$bank_accounts = $post->get_post_by_meta_data(['type' => 'pm_bank_account', 'status' => 'active']);
		$accounts = "";
		if(_count($bank_accounts)) {
			foreach ($bank_accounts as $bank) {
				$accounts .= $this->template->get_template("accounts", [
					'account_number' => $bank['account_number'],
					'account_name' => $bank['account_name'],
					'account_type' => $bank['account_type'],
					'bank_name' => $bank['bank_name']
				]);
			}
		}
		$placeholders['payment_methods'] = $this->template->get_template("payment-methods", ['accounts' => $accounts]);
		$path = get_option('home_url').get_option("company-logo");
		$type = pathinfo($path, PATHINFO_EXTENSION);
		$data = file_get_contents($path);
		$placeholders['company-logo'] = 'data:image/' . $type . ';base64,' . base64_encode($data);
		return $template->get_template("invoice", $placeholders);
	}

	public function full_receipt_template($user_meta = false) {
		$post = new post;
		$name_on_invoice = ($this->invoice->name == NULL || $this->invoice->name == "") ? $this->name : $this->invoice->name;
		$address_on_invoice = ($this->invoice->address == NULL || $this->invoice->address == "") ? $this->address : $this->invoice->address;
		$phone_on_invoice = ($this->invoice->phone == NULL || $this->invoice->phone == "") ? $this->phone : $this->invoice->phone;
		$email_on_invoice = ($this->invoice->email == NULL || $this->invoice->email == "") ? $this->email : $this->invoice->email;
		$bank_account = get_option('bank_accounts');
		$credits = $post->get_post_by_meta_data(['type' => 'credit', 'in_ref' => $this->invoice->invoice_number, 'status' => ['approved', 'confirmed']]);

		$html = '<table width="100%" cellspacing="0" cellpadding="0" border="0" bgcolor="#ffffff" style="font-size: 12px; border-color: #000;" >
		                  <tbody>
		                  	<tr>
		                        <td colspan="2" align="left" style="padding-bottom: 30px;border-bottom: solid 1px #d0d0d0;">
		                            <h1 style="font-weight: 800;font-size: 30px;line-height: 0.8;">RECEIPT</h1>
		                            <em>'.date("M d, Y", time()).'</em>
		                        </td>
		                        <td style="padding-bottom: 30px;border-bottom: solid 1px #d0d0d0;text-align: right;">
		                            <img style="width: '.apply_filters('company-logo-width', 120).'px;" src="'.get_option("company-logo").'"/>
		                        </td>
		                    </tr>
		                    <tr>
		                        <td colspan="2" align="left">
		                            <p>'.$name_on_invoice.'</p>
		                            <p>'.$address_on_invoice.'<br/>
										Phone: '.$phone_on_invoice.'<br/>
										Email: '.$email_on_invoice.'<br/>
									</p>
		                        </td>
		                        <td align="right">
		                            <p>
		                                #'.$this->invoice->invoice_number.' <br>
		                                Status: Paid <br>
		                                Due Date: '.date("M d, Y", $this->invoice->due_date).'
		                            </p>
		                        </td>
		                    </tr>
		                    <tr>
		                      <td colspan="3">'.$this->receipt_template($this->items).'</td>
		                    </tr>
		                    <tr>
		                      <td colspan="3">'.$this->payment_template($credits).'</td>
		                    </tr>
		                    <tr>
		                      <td colspan="3">
		                            <h4></h4>	                            
		                        </td>
		                    </tr>
		                  </tbody>
		                </table>';
		        return $html;
	}

	public function mail_invoice() {
		$user_meta = get_user_meta($this->invoice->user_id);
		$support = new support;
		$support->action = get_option("home_url")."invoice?id=".$this->invoice->id;
		$html = $this->invoice_template($user_meta);
		$settings = [
			'html' => $html,
			'pdf_header_title' => $this->invoice->invoice_number,
	        'pdf_subject' => $this->invoice->invoice_number,
			'output_file_name' => $this->invoice->invoice_number.'-invoice.pdf',
			'output_type' => 'F',
			'path' => ABSPATH.UPLOADFOLDER."/pdfs/invoices/"
		];
		make_pdf($settings, false, true);
		$path = UPLOADFOLDER."/pdfs/invoices/".$settings['output_file_name'];
		$attachments = array($path);
		$support->send_mail("INVOICE #".$this->invoice->invoice_number, [$user_meta], $html, array()); //, $attachments);
		unlink(ABSPATH.$path);
	}

	public function mail_receipt() {
		$user_meta = get_user_meta($this->invoice->user_id);
		$support = new support;
		$support->action = get_option("home_url")."invoice?id=".$this->invoice->id;
		$html = $this->full_receipt_template($user_meta);
		$settings = [
			'html' => $html,
			'pdf_header_title' => $this->invoice->invoice_number,
	        'pdf_subject' => $this->invoice->invoice_number,
			'output_file_name' => $this->invoice->invoice_number.'-receipt.pdf',
			'output_type' => 'F',
			'path' => ABSPATH.UPLOADFOLDER."/pdfs/receipts/"
		];
		make_pdf($settings, false, true);
		$path = UPLOADFOLDER."/pdfs/receipts/".$settings['output_file_name'];
		$attachments = array($path);
		$support->send_mail("RECEIPT #".$this->invoice->invoice_number, [$user_meta], $html, array(), $attachments);
	}

	public function remove_item($ref) {
		if(isset($this->items[$ref])) {
			unset($this->items[$ref]);
			str_replace("[".$ref."]", "", $this->refs);
			return true;
		} else return false;
	}

	public function has_credit_instance($ref, $approved = true) {
		$args = ['type' => 'credit', 'in_ref' => $ref];
		if($approved) {
			$args['status'] = ["approved", "confirmed"];
		} else {
			$args['status'] = ["approved", "confirmed", "pending", "submitted"];
		}
		return db_post_unique($args);
	}

	public function invoice_credit_instances($in_ref, $status = false) {
		$total = $this->total_invoice_credit($in_ref, $status);
		if((int) $total === 0) return false;
		if($this->total() > round($total, 2)) {
			return false;
		} else return true;
	}

	public function total_invoice_credit($in_ref = false, $status = false) {
		if(!$in_ref) {
			$in_ref = $this->invoice->invoice_number;
		}
		$post = new post;
		$args = ['type' => 'credit', 'in_ref' => $in_ref];
		if($status) {
			$args['status'] = $status;
		}
		$credits = $post->get_post_by_meta_data($args);
		$total = 0;
		if(_count($credits)) {
			if(is_array($credits)) {
				foreach ($credits as $meta) {
					$total += is_numeric($meta['amount']) ? $meta['amount'] : 0;
				}
			}
		}
		return round($total, 2);
	}

	public function new_invoice() {
		if(_count($this->items) < 1) {
			return false;
		}
		$user = new user;
		$items = serialize($this->items);
		$items = base64_encode($items);
		$invoice_number = $this->generate_invoice_number();
		$date_created = time();
		$status = "unpaid";

		$sql = "INSERT INTO invoice
				(`user_id`, `invoice_number`, `items`, `refs`, `success_method`, `error_method`, `date_created`, `due_date`, `status`, `name`, `address`, `email`, `phone`)
				VALUES
				(%d, %s, %s, %s, %s, %s, %d, %d, %s, %s, %s, %s, %s)";
		$this->query->query($this->query->prepare($sql, $this->user_id, $invoice_number, $items, $this->refs, $this->success_method, $this->error_method, $date_created, $this->due_date, $status, $this->name, $this->address, $this->email, $this->phone));
		$user->update_user_meta($this->user_id, ['debt_status' => 'in debt']);
		$this->invoice = new stdClass;
		$this->invoice->user_id = $this->user_id;
		$this->invoice->invoice_number = $invoice_number;
		$this->invoice->items = $items;
		$this->invoice->refs = $this->refs;
		$this->invoice->success_method = $this->success_method;
		$this->invoice->error_method = $this->error_method;
		$this->invoice->date_created = $date_created;
		$this->invoice->due_date = $this->due_date;
		$this->invoice->status = $status;
		$this->invoice->name = $this->name;
		$this->invoice->address = $this->address;
		$this->invoice->email = $this->email;
		$this->invoice->phone = $this->phone;
		$this->invoice->id = mysqli_insert_id($this->query->connection);
		$this->mail_invoice();
	}

	public function item_in_another_invoice($ref) {
		$sql = "SELECT * FROM invoice WHERE `user_id` = '$this->user_id' AND `refs` LIKE '%[$ref]%' LIMIT 1";
		$res = $this->query->get_results($sql);
		return _count($res) ? $res[0] : false;
	}

	public function remove_item_by_ref($ref) {
		$invoice = $this->item_in_another_invoice($ref);
		if($invoice) {
			if(!$this->has_credit_instance($ref, false)) {
				$this->set_invoice_by_number($invoice->invoice_number);
				$this->remove_item($ref);
				$this->save();
			} else return false;
		} else return false;
	}

	public function status($items = false, $absolute = false) {
		$status = (isset($this->invoice->status)) ? $this->invoice->status : "unpaid";
		$update_invoice = false;

		if(isset($this->status) && ($this->status === "paid") && !$absolute) {
			return $this->status;
		}
		$invoice_paid = $this->invoice_credit_instances($this->invoice->invoice_number, ["confirmed", "approved"]);
		if($invoice_paid) {
			$update_invoice = true;
			$this->status = "paid";
		} else {
			$items = ($items) ? $items : $this->items;
			if(_count($items) && is_array($items)) {
				foreach ($items as $key => &$value) {
					if($value['remark'] === "unpaid") {
						if($this->has_credit_instance($value['ref'], true)) {
							$value['remark'] = "paid";
							$update_invoice = true;
						} else {
							$status = "unpaid";
							$update_invoice = true;
						}
					}
				}
			} else return false;
			$this->status = $status;
		}
		
		if($this->invoice) {
			if($this->invoice->status !== $this->status) {
				$this->invoice->status = $this->status;
				$this->update();
			}
		}

		return $status;
	}

	public function save() {
		if($this->invoice) {
			$this->update();
		} else {
			$this->new_invoice();
		}
	}

	public function update() {
		if(_count($this->items) < 1) {
			$this->delete();
			return;
		}
		$items = serialize($this->items);
		$items = base64_encode($items);
		$sql = "UPDATE invoice
				SET `items` = %s,
				`refs` = %s,
				`status` = %s
				WHERE `invoice_number` = %s
				AND `user_id` = %d";
		$this->query->query($this->query->prepare($sql, $items, $this->refs, $this->status, $this->invoice->invoice_number, $this->user_id));
		$this->mail_invoice();
	}

	public function get_item($ref) {
		if(!isset($this->items[$ref])) return false;
		if(!is_array($this->items[$ref])) return false;
		return $this->items[$ref];
	}

	public function update_item($ref, $args = array()) {
		if(!$item = $this->get_item($ref)) return false;
		if(!is_array($args)) return false;
		$items = $this->items;
		$item = array_merge($item, $args);
		$items[$ref] = $item;
		$this->items = $items;
		$this->save();
	}

	public function delete() {
		if(!$this->invoice) {
			return false;
		}
		$sql = "DELETE FROM invoice
				WHERE `invoice_number` = %s
				AND `user_id` = %d";
		$this->query->query($this->query->prepare($sql, $this->invoice->invoice_number, $this->user_id));
	}

	public function set_invoice_by_number($invoice_number, $invoice_obj = false) {
		$run_query = false;
		if($invoice_obj) {
			if($invoice_obj->invoice_number === $invoice_number) {
				$this->invoice = $invoice_obj;
			} else $run_query = true;
		} else {
			$run_query = true;
		} 
		if($run_query) {
			$sql = "SELECT *
					FROM invoice
					WHERE `invoice_number` LIKE '$invoice_number'
					LIMIT 1";
			$res = $this->query->get_results($sql);
			$this->invoice = _count($res) ? array_shift($res) : false;
		} else {
			$this->invoice = $invoice_obj;
		}
		if($this->invoice) {
			$this->user_id = $this->invoice->user_id;
			$this->items = $this->unserialize_invoice_items($this->invoice->items);
			$this->refs = $this->invoice->refs;
			$this->status = $this->invoice->status;
			$this->check_for_discounts();
		}
	}

	public function get_invoice_by_number($invoice_number) {
		$this->set_invoice_by_number($invoice_number);
		return $this->invoice;
	}

	public function get_invoice_by_id($id) {
		$sql = "SELECT * FROM invoice
			WHERE `id` = %d
			LIMIT 1";
		$res = $this->query->get_results($this->query->prepare($sql, $id), OBJECT_K);
		$this->invoice = _count($res) ? array_shift($res) : false;
		if($this->invoice) {
			$this->user_id = $this->invoice->user_id;
			$this->items = $this->unserialize_invoice_items($this->invoice->items);
			$this->refs = $this->invoice->refs;
			$this->status = $this->invoice->status;
			$this->check_for_discounts();
		}
	}

	public function unserialize_invoice_items($items) {
		if(!is_serialized($items)) {
			$items = base64_decode($items);
		}
		return is_serialized($items) ? unserialize($items) : array();
	}

	public function invoice_exist($invoice_number) {
		$this->set_invoice_by_number($invoice_number);
		return $this->invoice;
	}

	public function generate_invoice_number() {
		do {
			$random = rand(1000, 9999).rand(1000, 9999).rand(1000, 9999).rand(1000, 9999);
			$invoice_number = $this->prefix.$random;
		} while($this->invoice_exist($invoice_number));
		return $invoice_number;
	}

	public function init() {
		$sql = "CREATE TABLE IF NOT EXISTS invoice (
				id BIGINT(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				user_id BIGINT(20) NOT NULL,
				invoice_number TEXT NOT NULL,
				items LONGTEXT NOT NULL,
				success_method TEXT NOT NULL,
				error_method TEXT NOT NULL,
				refs TEXT NOT NULL,
				due_date TEXT NOT NULL,
				date_created TEXT NOT NULL,
				status TEXT NOT NULL
				)";
		if (! mysqli_query($this->query->connection, $sql)) {
			die ('Unable to create invoice table');
		}

		if(!get_record("reviewed_invoice_table")) {
			$sql = "ALTER TABLE invoice
					ADD (
					name TEXT,
					address TEXT,
					email VARCHAR(50),
					phone VARCHAR(30))";
			if (! mysqli_query($this->query->connection, $sql)) {
				die ('Unable to alter invoice table');
			}
			update_record("reviewed_invoice_table", 1);
		}

		// stores wallet balance snapshot
		$sql = "CREATE TABLE IF NOT EXISTS ewallet_snapshot (
				id BIGINT(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				user_id BIGINT(20) NOT NULL,
				balance VARCHAR(60) NOT NULL,
				date_created TEXT NOT NULL
				)";
		if (! mysqli_query($this->query->connection, $sql)) {
			die ('Unable to create ewallet_snapshot table');
		}

		// stores balance brought forward
		$sql = "CREATE TABLE IF NOT EXISTS balance_bf (
				id BIGINT(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				ledger TEXT NOT NULL,
				outflow TEXT NOT NULL,
				inflow TEXT NOT NULL,
				balance TEXT NOT NULL,
				time_stop TEXT NOT NULL,
				last_post_id BIGINT(20) UNSIGNED NULL
				)";
		if (! mysqli_query($this->query->connection, $sql)) {
			die ('Unable to create balance_bf table');
		}

		// stores discounts
		$sql = "CREATE TABLE IF NOT EXISTS discount (
				id BIGINT(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				amount TEXT NOT NULL,
				invoice_number TEXT NOT NULL,
				ref TEXT NOT NULL,
				narration TEXT NOT NULL,
				status TEXT NOT NULL,
				applied_by BIGINT(20) NOT NULL
				-- date_created TEXT NOT NULL
				)";
		if (! mysqli_query($this->query->connection, $sql)) {
			die ('Unable to create discount table');
		}

		if(!get_record("date_created_to_discount_table")) {
			$sql = "ALTER TABLE discount
					ADD date_created TEXT NOT NULL";
			if (! mysqli_query($this->query->connection, $sql)) {
				die ('Unable to alter discount table');
			}
			update_record("date_created_to_discount_table", 1);
		}

		// stores debt
		$sql = "CREATE TABLE IF NOT EXISTS debt (
				id BIGINT(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
				amount TEXT NOT NULL,
				accural TEXT NOT NULL,
				user_id BIGINT(20) NOT NULL,
				ref TEXT NOT NULL,
				narration TEXT NOT NULL,
				status TEXT NOT NULL,
				rate INT(3) DEFAULT 0,
				moratorium TEXT NOT NULL,
				applied_by BIGINT(20) NOT NULL,
				date_created TEXT NOT NULL
				)";
		if (! mysqli_query($this->query->connection, $sql)) {
			die ('Unable to create debt table');
		}

		if(!$record = get_record("pm_check_due_debt")) {
			$cron = new cronjobs;
			$cron->schedule("*/5 * * * *", "pm_check_due_debt");
			update_record("pm_check_due_debt", 1);
		}

		if(!$record = get_record("pm_accure_interest_on_debt")) {
			$cron = new cronjobs;
			$cron->schedule("1 1 * * *", "pm_accure_interest_on_debt");
			update_record("pm_accure_interest_on_debt", 1);
		}
	}

	public function description() {
		if(isset($this->description)) {
			return $this->description;
		}
		$description = $this->invoice->invoice_number."<ul>";
		if(_count($this->items) && is_array($this->items)) {
			foreach ($this->items as $key => $value) {
				$description .= "<li>".$value['narration']."</li>";
			}
			// $description = substr($description, 0, -3);
		}
		$description .= "</ul>";
		$this->description = $description;
		return $description;
	}

	/**
	 * Get invoice total or balance
	 * @param  boolean $balance Set to true to get invoice balance or false to get the invoice total
	 * @return number           returns invoice total or balance
	 */
	public function total($balance = false) {
		if(isset($this->total)) {
			if($balance) {
				if(isset($this->balance)) {
					return $this->balance;
				} else {
					$this->get_balance($this->total);
				}
			} else {
				return $this->total;
			}
		}
		$total = 0;
		if(_count($this->items) && is_array($this->items)) {
			foreach ($this->items as $key => $value) {
				$total += ($value['amount'] * $value['quantity']);
			}
		}
		$this->total = $total;
		if($balance) {
			return $this->get_balance($total);
		}
		return round($total, 2);
	}

	/**
	 * [get_balance Function used to check invoice balance]
	 * @param  boolean/number $total Invoice total (optional)
	 * @return number         returns invoice balance
	 */
	public function get_balance($total = false) {
		if(!$total) {
			$total = $this->total;
		}
		$credits = $this->total_invoice_credit(false, ['submitted', 'confirmed']);
		$this->balance = $total - $credits;
		$this->balance = round($this->balance, 2);
		return $this->balance;
	}

	public function refs_array() {
		return explode("]", str_replace("[", "", substr($this->refs, 0, -1)));
	}

	public function process_success($args) {
		$successful = array();
		if(_count($this->items)) {
			foreach ($this->items as $item) {
				set_time_limit(200);
				$success_method = isset($item['success_method']) ? $item['success_method'] : "_pm_default_success_method";
				$error_method = isset($item['error_method']) ? $item['error_method'] : "_pm_default_error_method";
				if(function_exists($success_method)) {
					$res = $success_method($args, $item);
					if($res) {
						$successful[] = $item;
					} else {
						foreach ($successful as $item) {
							$error_method($args, $item);
						}
						return false;
					}
				}
			}
		}
		$this->status();
		// $this->check_debt_status();
		return (_count($successful) === _count($this->items)) ? true : false;
	}

	/**
	 * Used to check is a user is owing or not
	 * Gives clearance by registering the user_meta debt status as not in debt
	 * Else returns the status to in debt
	 * @return boolean, numeric  - Returns false if user is not in debt else returns the debt value
	 */
	public function check_debt_status() {
		$sql = "SELECT * FROM invoice WHERE `user_id` = '$this->user_id' AND `status` LIKE 'unpaid'";
		$res = $this->query->get_results($sql);
		$in_debt = false;
		$debt_figure = 0;
		if($res) {
			if(_count($res)) {
				foreach ($res as $inv2) {
					$instance = new self($this->user_id);
					$instance->set_invoice_by_number($inv2->invoice_number);
					$bal = $instance->total(true);
					if($bal > 0) {
						$in_debt = true;
						$debt_figure += $bal;
					}
				}
			}
		}
		$user = new user;
		if($in_debt) {
			$user->update_user_meta($this->user_id, ['debt_status' => 'in debt']);
			return $debt_figure;
		} else {
			$user->update_user_meta($this->user_id, ['debt_status' => 'not in debt']);
			return false;
		}
		
	}

	public function process_error($args) {
		$successful = array();
		if(_count($this->items) && is_array($this->items)) {
			foreach ($this->items as $item) {
				set_time_limit(200);
				$success_method = isset($item['success_method']) ? $item['success_method'] : "_pm_default_success_method";
				$error_method = isset($item['error_method']) ? $item['error_method'] : "_pm_default_error_method";
				if(function_exists($error_method)) {
					$res = $error_method($args, $item);
					if($res) {
						$successful[] = $item;
					}
				}
			}
		}
		$this->status();
		return (_count($successful) === _count($this->items)) ? true : false;
	}

	//discounts
	public function apply_discount($discount, $narration, $ref = false, $mode = "flat") {
		if(!$this->invoice) return false;
		$amount = $this->total(true);
		if(strtolower($mode) === "percentage") {
			//convert discount to flat
			if($amount <= 0) return false;
			$discount = 0.01 * $discount * $amount;
		}
		if($discount > $amount) return false;
		if(!$ref) {
			$ref = $this->generate_discount_ref();
		} else {
			if($this->discount_applied($ref)) return false;
		}
		return $this->query->insert(
			"discount",
			["amount", "invoice_number", "ref", "narration", "status", "applied_by", "date_created"],
			[$discount, $this->invoice->invoice_number, $ref, $narration, "approved", $this->session->id, time()]
		);
	}

	private function generate_discount_ref() {
		do {
			$ref = "DSC-".rand(111, 999)."-".rand(111, 999)."-".rand(111, 999);
		} while ($this->discount_applied($ref));
		return $ref;
	}

	private function discount_applied($ref) {
		if(!$this->invoice) return false;
		$res =	$this->query->quick_select(
					"discount", 
					[
						"where" => ["ref" => $ref, "invoice_number" => $this->invoice->invoice_number]
					], OBJECT_K
				);
		return (_count($res)) ? $res[0] : false;
	}

	private function check_for_discounts() {
		if(!$this->invoice) return false;
		$res =	$this->query->quick_select(
					"discount", 
					[
						"where" => ["invoice_number" => $this->invoice->invoice_number, "status" => ["approved", "redeemed"]]
					], OBJECT_K
				);
		if(_count($res)) {
			foreach ($res as $discount) {
				$amount = 0 - $discount->amount;
				$remark = ($discount->status == 'redeemed') ? 'paid' : 'unpaid';
				$this->items[$discount->ref] = [
					'amount' => $amount,
					'narration' => "Discount/Promo: ".$discount->narration,
					'feeid' => $discount->id,
					'ref' => $discount->ref,
					'quantity' => 1,
					'remark' => $remark,
					'success_method' => '_pm_discount_success_method',
					'error_method' => '_pm_discount_error_method'
				];
				$this->refs = str_replace("[".$discount->ref."]", "", $this->refs);
				$this->refs .= "[".$discount->ref."]";
			}
		}
	}

	public function get_discount($invoice_number = false) {
		if(!$invoice_number) {
			if(!$this->invoice) return false;
			$invoice_number = $this->invoice->invoice_number;
		}
		$res =	$this->query->quick_select(
					"discount", 
					[
						"where" => ["invoice_number" => $invoice_number]
					], OBJECT_K
				);
		return (_count($res)) ? $res : false;
	}

	public function total_discount($invoice_number = false) {
		$total = 0;
		$discounts = $this->get_discount($invoice_number);
		if($discounts) {
			foreach ($discounts as $key => $meta) {
				$total += $meta->amount;
			}
		}
		return $total;
	}

	//debts
	public function apply_debt($amount, $narration, $rate = 0, $ref = false, $moratorium = false) {
		if(!$this->invoice) return false;
		$moratorium = !$moratorium ? time() : $moratorium;
		if(!$ref) {
			$ref = $this->generate_debt_ref();
		} else {
			if($this->debt_applied($ref)) return false;
		}
		$this->query->insert(
			"debt",
			["amount", "accural", "user_id", "ref", "narration", "status", "rate", "moratorium", "applied_by", "date_created"],
			[$amount, $amount, $this->invoice->user_id, $ref, $narration, "pending", $rate, $moratorium, $this->session->id, time()]
		);
	}

	private function generate_debt_ref() {
		do {
			$ref = "DBT-".rand(111, 999)."-".rand(111, 999)."-".rand(111, 999);
		} while ($this->debt_applied($ref));
		return $ref;
	}

	private function debt_applied($ref) {
		$res =	$this->query->quick_select(
					"debt", 
					[
						"where" => ["ref" => $ref]
					], OBJECT_K
				);
		return (_count($res)) ? $res[0] : false;
	}
}