<?php
namespace Avirtum\GutenDraw\Models;

defined( 'ABSPATH' ) || exit;

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

    public const META_EDITED_BY = '_gutendraw_edited_by';
    public const META_ROLE = '_gutendraw_role';
    public const META_FILES = '_gutendraw_files';
    public const META_SVG_IMAGE = '_gutendraw_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
            $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_EDIT ) ) {
            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;
    }
}