<?php

namespace Groundhogg\DB;

// Exit if accessed directly
use Groundhogg\Contact;
use Groundhogg\Contact_Query;
use Groundhogg\DB\Query\Table_Query;
use Groundhogg\Preferences;
use function Groundhogg\add_action_use_once;
use function Groundhogg\get_primary_owner_id;
use function Groundhogg\is_free_email_provider;
use function Groundhogg\safe_user_id_sync;


if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Contact DB
 *
 * Store contact info
 *
 * @since       File available since Release 0.1
 * @subpackage  includes/DB
 * @author      Adrian Tobey <info@groundhogg.io>
 * @copyright   Copyright (c) 2018, Groundhogg Inc.
 * @license     https://opensource.org/licenses/GPL-3.0 GNU Public License v3
 * @package     Includes
 */
class Contacts extends DB {

	/**
	 * The metadata type.
	 *
	 * @access public
	 * @since  2.8
	 * @var string
	 */
	public $meta_type = 'contact';

	/**
	 * The name of the date column.
	 *
	 * @access public
	 * @since  2.8
	 * @var string
	 */
	public $date_key = 'date_created';

	/**
	 * Get the DB suffix
	 *
	 * @return string
	 */
	public function get_db_suffix() {
		return 'gh_contacts';
	}

	/**
	 * Get the DB primary key
	 *
	 * @return string
	 */
	public function get_primary_key() {
		return 'ID';
	}

	/**
	 * Get the DB version
	 *
	 * @return mixed
	 */
	public function get_db_version() {
		return '2.0';
	}

	/**
	 * Get the object type we're inserting/updateing/deleting.
	 *
	 * @return string
	 */
	public function get_object_type() {
		return 'contact';
	}

	/**
	 * Update contact record when user profile updated.
	 */
	protected function add_additional_actions() {
		add_action( 'groundhogg/owner_deleted', [ $this, 'owner_deleted' ], 10, 2 );
//		add_action( 'delete_user', [ $this, 'user_deleted' ], 10, 1 );
		parent::add_additional_actions();
	}

	/**
	 * When an owner is deleted, reassign their contacts
	 *
	 * @param $prev
	 * @param $new
	 *
	 * @return void
	 */
	public function owner_deleted( $prev, $new ) {
		$this->update( [
			'owner_id' => $prev,
		], [
			'owner_id' => $new,
		] );
	}

	/**
	 * When a user is deleted, also remove the relationship in the contacts table
	 *
	 * @param $id
	 *
	 * @return void
	 */
	public function user_deleted( $id ) {
		$this->update( [
			'user_id' => $id,
		], [
			'user_id' => 0,
		] );
	}

	public function create_object( $object ) {
		return new Contact( $object ); // TODO: Change the autogenerated stub
	}

	/**
	 * Get columns and formats
	 *
	 * @access  public
	 * @since   2.1
	 */
	public function get_columns() {
		return [
			'ID'                        => '%d',
			'email'                     => '%s',
			'is_free' => '%d',
			'first_name'                => '%s',
			'last_name'                 => '%s',
			'user_id'                   => '%d',
			'owner_id'                  => '%d',
			'optin_status'              => '%d',
			'date_created'              => '%s',
			'date_optin_status_changed' => '%s',
		];
	}

	/**
	 * Get default column values
	 *
	 * @access  public
	 * @since   2.1
	 */
	public function get_column_defaults() {
		return array(
			'ID'                        => 0,
			'email'                     => '',
			'is_free' => 0,
			'first_name'                => '',
			'last_name'                 => '',
			'user_id'                   => 0,
			'owner_id'                  => current_user_can( 'add_contacts' ) ? get_current_user_id() : get_primary_owner_id(),
			'optin_status'              => Preferences::UNCONFIRMED,
			'date_created'              => current_time( 'mysql' ),
			'date_optin_status_changed' => current_time( 'mysql' ),
		);
	}

	/**
	 * Add a contact
	 *
	 * @access  public
	 * @since   2.1
	 */
	public function add( $data = array() ) {

		if ( empty( $data['email'] ) ) {
			$this->last_error = 'No email field provided.';

			return false;
		}

		// fallback to set `is_free` when adding a new contact record
		if ( ! isset( $data['is_free'] ) ) {
			$data['is_free'] = is_free_email_provider( $data['email'] );
		}

		$data = $this->sanitize_columns( $data );

		return $this->insert_on_duplicate_key_update( $data );
	}

	/**
	 * Inserts, on duplicate it updates
	 *
	 * @param $data
	 *
	 * @return int
	 */
	public function insert_on_duplicate_key_update( $data ) {

		$orig_data = $data;

		// Initialise column format array
		$column_formats  = $this->get_columns();
		$column_defaults = $this->get_column_defaults();

		// Orig data does not get parsed
		$data = wp_parse_args( $data, $column_defaults );

		// Force fields to lower case
		$data      = array_change_key_case( $data );
		$orig_data = array_change_key_case( $orig_data );

		// White list columns
		$data      = array_intersect_key( $data, $column_formats );
		$orig_data = array_intersect_key( $orig_data, $column_formats );

		// Function to rewrite the INSERT query to include ON DUPLICATE KEY UPDATE
		$update_func = function ( $query ) use ( $orig_data, $column_formats, $column_defaults ) {

			// Not an insert query
			if ( ! preg_match( '/^INSERT/i', $query ) ) {
				return $query;
			}

			// Don't update with default values
			$update_data = array_diff_assoc( $orig_data, $column_defaults );

			global $wpdb;

			// never update these columns
			unset( $update_data['ID'] );
			unset( $update_data['email'] );
			unset( $update_data['date_created'] );

			// No data to update if key exists already
			if ( empty( $update_data ) ) {
				return $query;
			}

			$pairs = [];

			foreach ( $update_data as $column => $value ) {
				$format  = $column_formats[ $column ];
				$pairs[] = $wpdb->prepare( "$column = $format", $value );
			}

			$query .= ' ON DUPLICATE KEY UPDATE ' . implode( ', ', $pairs );

			return $query;
		};

		add_filter( 'query', $update_func );

		$inserted = $this->insert( $data );

		remove_filter( 'query', $update_func );

		return $inserted;
	}

	/**
	 * Update a contact
	 *
	 * @access  public
	 * @since   2.1
	 * @return  bool
	 */
	public function update( $row_id_or_where = 0, $data = [], $where = [] ) {

		$data = $this->sanitize_columns( $data );

		// where based, not ID based
		if ( is_array( $row_id_or_where ) || ( ! empty( $where ) && is_array( $where ) ) ) {

			// don't allow bulk updating some columns
			unset( $data['ID'] );
			unset( $data['email'] );

			// don't allow bulk updating with the same user_id unless it's 0
			if ( ! empty( $data['user_id'] ) ) {
				unset( $data['user_id'] );
			}

			return parent::update( $row_id_or_where, $data, $where );
		}

		$column = ! empty( $where ) && is_string( $where ) ? $where : $this->primary_key;

		if ( isset( $data['email'] ) ) {

			// prevent empty email addresses
			if ( empty( $data['email'] ) ) {
				unset( $data['email'] );
			}

			// check to see if this email address is already in use
			$query = new Table_Query( $this );
			$query->where()->notEquals( $column, $row_id_or_where )->equals( 'email', $data['email'] );
			if ( $query->count() > 0 ) {
				unset( $data['email'] );
			}
		}

		if ( ! empty( $data['user_id'] ) ) {

			// check to see if this user_id is being used by another contact
			$query = new Table_Query( $this );

			$query->where()->notEquals( $column, $row_id_or_where )->equals( 'user_id', $data['user_id'] );

			// it is being used by another contact :/
			if ( $query->count() > 0 ) {
				unset( $data['user_id'] );
				// let's safely resync the IDs after this update is complete.
				$user_id    = absint( $data['user_id'] );
				$contact_id = absint( $row_id_or_where );

				add_action_use_once( 'groundhogg/db/post_update/contact', fn() => safe_user_id_sync( $user_id, $contact_id ) );
			}
		}

		return parent::update( $row_id_or_where, $data, $where );
	}

	/**
	 * Checks if a contact exists
	 *
	 * @access  public
	 * @since   2.1
	 */
	public function exists( $value = '', $field = 'email' ) {
		return parent::exists( $value, $field );
	}

	/**
	 * Retrieves a single contact from the database
	 *
	 * @access public
	 *
	 * @since  2.3
	 *
	 * @param mixed  $value The Customer ID or email to search
	 *
	 * @param string $field id or email
	 *
	 * @return mixed          Upon success, an object of the contact. Upon failure, NULL
	 */
	public function get_contact_by( $field = 'ID', $value = 0 ) {
		if ( empty( $field ) || empty( $value ) ) {
			return null;
		}

		return parent::get_by( $field, $value );
	}

	/**
	 * Use contact query calss
	 *
	 * @param array  $data
	 * @param string $order
	 *
	 * @return array|bool|object|null
	 */
	public function query( $data = [], $order = '', $from_cache = false ) {
		$data  = $this->prepare_contact_query_args( $data );
		$query = new Contact_Query();

		return $query->query( $data );
	}

	/**
	 * Count the total number of contacts in the database
	 *
	 * @access  public
	 * @since   2.1
	 */
	public function count( $args = array() ) {
		$args           = $this->prepare_contact_query_args( $args );
		$args['count']  = true;
		$args['offset'] = 0;

		unset( $args['limit'] );
		unset( $args['number'] );

		$query = new Contact_Query( $args );

		return $query->count();
	}

	/**
	 * Prepare query arguments for `WPGH_Contact_Query`.
	 *
	 * This method ensures that old arguments transition seamlessly to the new system.
	 *
	 * @access protected
	 *
	 * @since  2.8
	 *
	 * @param array $args Arguments for `WPGH_Contact_Query`.
	 *
	 * @return array Prepared arguments.
	 */
	protected function prepare_contact_query_args( $args ) {
		if ( ! empty( $args['ID'] ) ) {
			$args['include'] = $args['ID'];
			unset( $args['ID'] );
		}

		if ( ! empty( $args['user_id'] ) ) {
			$args['users_include'] = $args['user_id'];
			unset( $args['user_id'] );
		}

		if ( ! empty( $args['date'] ) ) {
			$date_query = array( 'relation' => 'AND' );

			if ( is_array( $args['date'] ) ) {
				$date_query[] = array(
					'after'     => date( 'Y-m-d 00:00:00', strtotime( $args['date']['start'] ) ),
					'inclusive' => true,
				);
				$date_query[] = array(
					'before'    => date( 'Y-m-d 23:59:59', strtotime( $args['date']['end'] ) ),
					'inclusive' => true,
				);
			} else {
				$date_query[] = array(
					'year'  => date( 'Y', strtotime( $args['date'] ) ),
					'month' => date( 'm', strtotime( $args['date'] ) ),
					'day'   => date( 'd', strtotime( $args['date'] ) ),
				);
			}

			if ( empty( $args['date_query'] ) ) {
				$args['date_query'] = $date_query;
			} else {
				$args['date_query'] = array(
					'relation' => 'AND',
					$date_query,
					$args['date_query'],
				);
			}

			unset( $args['date'] );
		}

		return $args;
	}

	public function create_table_sql_command() {
		return "CREATE TABLE " . $this->table_name . " (
		ID bigint(20) unsigned NOT NULL AUTO_INCREMENT,
		email varchar(50) NOT NULL,
		first_name mediumtext NOT NULL,
		last_name mediumtext NOT NULL,
		user_id bigint(20) unsigned NOT NULL,
		owner_id bigint(20) unsigned NOT NULL,
		optin_status int unsigned NOT NULL,
		is_free tinyint(1) unsigned NOT NULL,
		date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
		date_optin_status_changed datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
		PRIMARY KEY (ID),
		UNIQUE KEY email (email),
		KEY user (user_id),
		KEY is_free_inbox (is_free),
		KEY owner_id (owner_id),
		KEY optin_status (optin_status),
		KEY date_created (date_created),
		KEY date_optin_status_changed (date_optin_status_changed)
		) {$this->get_charset_collate()};";
	}

	/**
	 * Sanitize the given columns
	 *
	 * @param $cols
	 *
	 * @return array
	 */
	public function sanitize_columns( $cols ) {

		foreach ( $cols as $key => $val ) {

			switch ( $key ) {

				case 'first_name':
				case 'last_name' :
					$cols[ $key ] = sanitize_text_field( $val );
					break;
				case 'email':
					$cols[ $key ] = strtolower( sanitize_email( $val ) );
					break;
				case 'optin_status':
					$cols[ $key ] = Preferences::sanitize( $val );
					break;
				case 'owner_id':
				case 'user_id':
					$cols[ $key ] = absint( $val );
					break;
				case 'is_free':
					$cols[ $key ] = (bool) $val;
					break;
			}

		}

		return $cols;

	}
}
