<?php

namespace Yalogica\NinjaDraw\Models;

defined('ABSPATH') || exit;

class DataModel
{
  public const POST_TYPE = 'ninjadraw';
  public const POST_STATUS_DRAFT = 'draft';
  public const POST_STATUS_PUBLISHED = 'publish';
  public const POST_STATUS_TRASH = 'trash';

  public const META_EDITED_BY = '_ninjadraw_edited_by';
  public const META_ROLE = '_ninjadraw_role';
  public const META_FILES = '_ninjadraw_files';
  public const META_SVG_IMAGE = '_ninjadraw_svg';

  private static function getTotal($filter, $post_statuses, $search = '%')
  {
    $permissionType = AccessModel::getCurrentUserPermissionType();

    if ($permissionType) {
      global $wpdb;
      $placeholder = implode(',', array_fill(0, count($post_statuses), '%s'));

      switch ($permissionType) {
        case AccessModel::ACCESS_TYPE_EDIT_ALL: {
            $sql = "
                        SELECT COUNT(P.id) as total
                        FROM {$wpdb->posts} AS P 
                        WHERE P.post_type = %s AND P.post_status IN ({$placeholder}) AND IFNULL(P.post_title,'') LIKE %s"
              . ($filter == 'mine' ? " AND P.post_author=%d" : " AND 0=%d");

            $args = [
              self::POST_TYPE,
              ...$post_statuses,
              $search,
              $filter == 'mine' ? get_current_user_id() : 0
            ];
          }
          break;
        case AccessModel::ACCESS_TYPE_EDIT_BY_ROLE: {
            $sql = "
                        SELECT COUNT(P.id) as total
                        FROM {$wpdb->posts} AS P
                        LEFT JOIN {$wpdb->postmeta} AS MUR
                        ON MUR.post_id=P.id AND MUR.meta_key=%s 
                        WHERE MUR.meta_value = %s AND P.post_type = %s AND P.post_status IN ({$placeholder}) AND IFNULL(P.post_title,'') LIKE %s"
              . ($filter == 'mine' ? " AND P.post_author=%d" : " AND 0=%d");

            $args = [
              self::META_ROLE,
              AccessModel::getCurrentUserRole(),
              self::POST_TYPE,
              ...$post_statuses,
              $search,
              $filter == "mine" ? get_current_user_id() : 0
            ];
          }
          break;
        case AccessModel::ACCESS_TYPE_EDIT_PRIVATE: {
            $sql = "
                        SELECT COUNT(P.id) as total
                        FROM {$wpdb->posts} AS P
                        WHERE P.post_author = %d AND P.post_type = %s AND P.post_status IN ({$placeholder}) AND IFNULL(P.post_title,'') LIKE %s";

            $args = [
              get_current_user_id(),
              self::POST_TYPE,
              ...$post_statuses,
              $search
            ];
          }
          break;
      }

      // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
      $sql = $wpdb->prepare($sql, ...$args);
      return $wpdb->get_var($sql);
      // phpcs:enable
    }

    return null;
  }

  private static function getRows($filter, $post_statuses, $search, $orderby, $order, $offset, $count)
  {
    $permissionType = AccessModel::getCurrentUserPermissionType();

    if ($permissionType) {
      global $wpdb;
      $placeholder = implode(',', array_fill(0, count($post_statuses), '%s'));

      switch ($permissionType) {
        case AccessModel::ACCESS_TYPE_EDIT_ALL: {
            $sql = "
                        SELECT P.id as id, P.post_title AS title, P.post_status AS status, P.post_author AS author, MLE.meta_value AS edited_by, MUR.meta_value AS role, P.post_date AS created, P.post_modified AS edited
                        FROM {$wpdb->posts} AS P
                        LEFT JOIN {$wpdb->postmeta} AS MLE
                        ON MLE.post_id = P.id AND MLE.meta_key = %s
                        LEFT JOIN {$wpdb->postmeta} AS MUR
                        ON MUR.post_id = P.id AND MUR.meta_key = %s
                        WHERE P.post_type=%s AND P.post_status IN ({$placeholder}) AND IFNULL(P.post_title,'') LIKE %s"
              . ($filter == 'mine' ? " AND P.post_author=%d" : " AND 0=%d") . "
                        ORDER BY %1s %1s
                        LIMIT %d, %d";

            $args = [
              self::META_EDITED_BY,
              self::META_ROLE,
              self::POST_TYPE,
              ...$post_statuses,
              $search,
              $filter == 'mine' ? get_current_user_id() : 0,
              $orderby,
              $order,
              $offset,
              $count
            ];
          }
          break;
        case AccessModel::ACCESS_TYPE_EDIT_BY_ROLE: {
            $sql = "
                        SELECT P.id as id, P.post_title AS title, P.post_status AS status, P.post_author AS author, MLE.meta_value AS edited_by, MUR.meta_value AS role, P.post_date AS created, P.post_modified AS edited
                        FROM {$wpdb->posts} AS P
                        LEFT JOIN {$wpdb->postmeta} AS MLE
                        ON MLE.post_id = P.id AND MLE.meta_key = %s
                        LEFT JOIN {$wpdb->postmeta} AS MUR
                        ON MUR.post_id = P.id AND MUR.meta_key = %s
                        WHERE MUR.meta_value = %s AND P.post_type = %s AND P.post_status IN ({$placeholder}) AND IFNULL(P.post_title,'') LIKE %s"
              . ($filter == 'mine' ? " AND P.post_author=%d" : " AND 0=%d") . "
                        ORDER BY %1s %1s
                        LIMIT %d, %d";

            $args = [
              self::META_EDITED_BY,
              self::META_ROLE,
              AccessModel::getCurrentUserRole(),
              self::POST_TYPE,
              ...$post_statuses,
              $search,
              $filter == 'mine' ? get_current_user_id() : 0,
              $orderby,
              $order,
              $offset,
              $count
            ];
          }
          break;
        case AccessModel::ACCESS_TYPE_EDIT_PRIVATE: {
            $sql = "
                        SELECT P.id as id, P.post_title AS title, P.post_status AS status, P.post_author AS author, MLE.meta_value AS edited_by, MUR.meta_value AS role, P.post_date AS created, P.post_modified AS edited
                        FROM {$wpdb->posts} AS P
                        LEFT JOIN {$wpdb->postmeta} AS MLE
                        ON MLE.post_id = P.id AND MLE.meta_key = %s
                        LEFT JOIN {$wpdb->postmeta} AS MUR
                        ON MUR.post_id = P.id AND MUR.meta_key = %s
                        WHERE P.post_author = %d AND P.post_type = %s AND P.post_status IN ({$placeholder}) AND IFNULL(P.post_title,'') LIKE %s
                        ORDER BY %1s %1s
                        LIMIT %d, %d";

            $args = [
              self::META_EDITED_BY,
              self::META_ROLE,
              get_current_user_id(),
              self::POST_TYPE,
              ...$post_statuses,
              $search,
              $orderby,
              $order,
              $offset,
              $count
            ];
          }
          break;
      }

      // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
      $sql = $wpdb->prepare($sql, ...$args);
      return $wpdb->get_results($sql, 'ARRAY_A');
      // phpcs:enable
    }

    return null;
  }

  public static function getItems($page, $perpage = 10, $filter = 'all', $orderby = null, $order = null, $search = null)
  {
    global $wpdb;

    $page = intval($page, 10);
    $page = $page == 0 ? 1 : $page;
    $count = intval($perpage, 10);
    $offset = ($page - 1) * $count;

    $filters_valid = ['all', 'mine', 'published', 'draft', 'trash'];
    $filter = in_array($filter, $filters_valid) ? $filter : $filters_valid[0];

    switch ($filter) {
      case 'all':
        $post_statuses = [self::POST_STATUS_PUBLISHED, self::POST_STATUS_DRAFT];
        break;
      case 'mine':
        $post_statuses = [self::POST_STATUS_PUBLISHED, self::POST_STATUS_DRAFT];
        break;
      case 'published':
        $post_statuses = [self::POST_STATUS_PUBLISHED];
        break;
      case 'draft':
        $post_statuses = [self::POST_STATUS_DRAFT];
        break;
      case 'trash':
        $post_statuses = [self::POST_STATUS_TRASH];
        break;
    }

    $orderby_valid = ['title', 'created', 'edited'];
    $orderby = in_array($orderby, $orderby_valid) ? $orderby : 'created';
    switch ($orderby) {
      case 'title':
        $orderby = 'post_title';
        break;
      case 'date':
        $orderby = 'post_date';
        break;
    }

    $order_valid = ['asc', 'desc'];
    $order = in_array($order, $order_valid) ? $order : 'desc';

    $search  = $search ? '%' . $wpdb->esc_like(sanitize_text_field($search)) . '%' : '%';

    $total = self::getTotal($filter, $post_statuses, $search);
    $items = self::getRows($filter, $post_statuses, $search, $orderby, $order, $offset, $count);

    $data = null;
    if (!$wpdb->last_error) {
      $data['total'] = intval($total, 10);
      $data['pages'] = $count ? ceil($data['total'] / $count) : 1;
      $data['page'] = $page;
      $data['items'] = $items;
    }
    return $data;
  }

  public static function getItemsCount()
  {
    global $wpdb;

    $sql = "
            SELECT COUNT(P.id) as total
            FROM {$wpdb->posts} AS P
            WHERE P.post_type = %s";

    $args = [
      self::POST_TYPE
    ];

    // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $sql = $wpdb->prepare($sql, ...$args);
    $count = intval($wpdb->get_var($sql));
    // phpcs:enable

    return $count;
  }

  public static function getFilterCounts()
  {
    return [
      'all' => self::getTotal('all', [self::POST_STATUS_PUBLISHED, self::POST_STATUS_DRAFT]),
      'mine' => self::getTotal('mine', [self::POST_STATUS_PUBLISHED, self::POST_STATUS_DRAFT]),
      'published' => self::getTotal('published', [self::POST_STATUS_PUBLISHED]),
      'draft' => self::getTotal('draft', [self::POST_STATUS_DRAFT]),
      'trash' => self::getTotal('all', [self::POST_STATUS_TRASH])
    ];
  }

  public static function getCurrentUserItems()
  {
    $permissionType = AccessModel::getCurrentUserPermissionType();

    if ($permissionType) {
      global $wpdb;

      switch ($permissionType) {
        case AccessModel::ACCESS_TYPE_EDIT_ALL: {
            $sql = "
                        SELECT P.id as id, P.post_title AS title
                        FROM {$wpdb->posts} P
                        WHERE P.post_type = %s AND P.post_status = %s
                        ORDER BY P.post_title";

            $args = [
              self::POST_TYPE,
              self::POST_STATUS_PUBLISHED
            ];
          }
          break;
        case AccessModel::ACCESS_TYPE_EDIT_BY_ROLE: {
            $sql = "
                        SELECT P.id as id, P.post_title AS title
                        FROM {$wpdb->posts} P
                        LEFT JOIN {$wpdb->postmeta} AS MUR
                        ON MUR.post_id = P.id AND MUR.meta_key = %s
                        WHERE MUR.meta_value = %s AND P.post_type = %s AND P.post_status = %s
                        ORDER BY P.post_title";

            $args = [
              self::META_ROLE,
              AccessModel::getCurrentUserRole(),
              self::POST_TYPE,
              self::POST_STATUS_PUBLISHED
            ];
          }
          break;
        case AccessModel::ACCESS_TYPE_EDIT_PRIVATE: {
            $sql = "
                        SELECT P.id as id, P.post_title AS title
                        FROM {$wpdb->posts} P
                        WHERE P.post_author = %d AND P.post_type = %s AND P.post_status = %s
                        ORDER BY P.post_title";

            $args = [
              get_current_user_id(),
              self::POST_TYPE,
              self::POST_STATUS_PUBLISHED
            ];
          }
          break;
      }

      // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
      $sql = $wpdb->prepare($sql, ...$args);
      $items = $wpdb->get_results($sql, 'ARRAY_A');
      // phpcs:enable

      if (!$wpdb->last_error) {
        return $items;
      }
    }

    return null;
  }

  public static function currentUserCan($post_id, $right)
  {
    $permissionType = AccessModel::getCurrentUserPermissionType();

    switch ($permissionType) {
      case AccessModel::ACCESS_TYPE_EDIT_ALL:
        return AccessModel::currentUserCan($right);

      case AccessModel::ACCESS_TYPE_EDIT_BY_ROLE:
        $role = get_post_meta($post_id, self::META_ROLE, true);
        if ($role === AccessModel::getCurrentUserRole()) {
          return AccessModel::currentUserCan($right);
        }

      case AccessModel::ACCESS_TYPE_EDIT_PRIVATE:
        $post = get_post($post_id);
        if (!$post) {
          return false;
        }
        if ($post->post_author === get_current_user_id()) {
          return AccessModel::currentUserCan($right);
        }
    }

    return false;
  }

  public static function getItem($id)
  {
    $post = get_post($id);
    if ($post && $post->post_type === self::POST_TYPE) {
      $data = [];
      $data['title'] = $post->post_title;
      $data['status'] = $post->post_status;
      $data['source'] = $post->post_content;
      $data['files'] = get_post_meta($id, self::META_FILES, true);

      return $data;
    }
    return null;
  }

  public static function createItem($data)
  {
    if (is_array($data) && AccessModel::currentUserCan(AccessModel::ACCESS_RIGHT_CREATE)) {
      $post = [
        'post_type' => self::POST_TYPE,
        'post_name' => uniqid(),
        'post_status' => self::POST_STATUS_DRAFT,
        'post_mime_type' => 'application/json',
        'post_title' => sanitize_text_field($data['title']),
        'post_content' => wp_slash($data['source']),
        'meta_input' => [
          self::META_EDITED_BY => get_current_user_id(),
          self::META_ROLE => AccessModel::getCurrentUserRole(),
          self::META_FILES => wp_slash($data['files']),
          self::META_SVG_IMAGE => wp_slash($data['svg']),
        ]
      ];
      $post_id = wp_insert_post($post);

      if ($post_id !== null) {
        return $post_id;
      }
    }
    return null;
  }

  public static function trashItem($id)
  {
    if (self::currentUserCan($id, AccessModel::ACCESS_RIGHT_DELETE)) {
      $post = get_post($id);
      if (!$post || $post->post_type !== self::POST_TYPE) {
        return;
      }

      wp_trash_post($id);
    }
  }

  public static function restoreItem($id)
  {
    if (self::currentUserCan($id, AccessModel::ACCESS_RIGHT_DELETE)) {
      $post = get_post($id);
      if (!$post || $post->post_type !== self::POST_TYPE) {
        return;
      }

      wp_untrash_post($id);
    }
  }

  public static function deleteItem($id)
  {
    if (self::currentUserCan($id, AccessModel::ACCESS_RIGHT_DELETE)) {
      $post = get_post($id);
      if (!$post || $post->post_type !== self::POST_TYPE) {
        return;
      }

      wp_delete_post($id, true);
    }
  }

  public static function copyItem($id)
  {
    if (!self::currentUserCan($id, AccessModel::ACCESS_RIGHT_CREATE)) {
      return null;
    }

    $post = get_post($id);
    if (!$post || $post->post_type !== self::POST_TYPE) {
      return null;
    }

    $title = $post->post_title . (stripos($post->post_title, '[Copy]') ? '' : ' [Copy]');

    $post = [
      'post_type' => self::POST_TYPE,
      'post_name' => uniqid(),
      'post_status' => self::POST_STATUS_DRAFT,
      'post_mime_type' => 'application/json',
      'post_title'  => $title,
      'post_content' => $post->post_content
    ];
    $post_id = wp_insert_post($post);

    $post_meta = get_post_custom($id);
    foreach ($post_meta as $key => $values) {
      foreach ($values as $value) {
        add_post_meta($post_id, $key, $value, true);
      }
    }

    if ($post_id !== null) {
      return $post_id;
    }

    return null;
  }

  public static function updateItem($id, $data)
  {
    $post = get_post($id);
    if (!$post || $post->post_type !== self::POST_TYPE) {
      return false;
    }

    if (is_array($data) && self::currentUserCan($id, AccessModel::ACCESS_RIGHT_EDIT)) {
      $post = [
        'ID' => $id,
        'post_title' => sanitize_text_field($data['title']),
        'post_content' => wp_slash($data['source']),
        'meta_input' => [
          self::META_EDITED_BY => get_current_user_id(),
          self::META_FILES => wp_slash($data['files']),
          self::META_SVG_IMAGE => wp_slash($data['svg'])
        ]
      ];
      $post_id = wp_update_post($post);

      if ($post_id !== null) {
        return true;
      }
    }

    return false;
  }

  public static function updateItemStatus($id, $status)
  {
    $post = get_post($id);
    if (!$post || $post->post_type !== self::POST_TYPE) {
      return false;
    }

    if (in_array($status, [self::POST_STATUS_DRAFT, self::POST_STATUS_PUBLISHED]) && self::currentUserCan($id, AccessModel::ACCESS_RIGHT_PUBLISH)) {
      $post = [
        'ID' => $id,
        'post_status' => sanitize_key($status)
      ];
      $post_id = wp_update_post($post);

      if ($post_id !== null) {
        return true;
      }
    }

    return false;
  }

  public static function getItemImage($id)
  {
    $post = get_post($id);
    if (!$post || $post->post_type !== self::POST_TYPE || $post->post_status !== self::POST_STATUS_PUBLISHED) {
      return null;
    }

    $image = get_post_meta($id, self::META_SVG_IMAGE, true);
    if ($image) {
      return $image;
    }

    return null;
  }
}
