<?php
namespace Bookly\Lib\Entities;

use Bookly\Lib;

class Staff extends Lib\Base\Entity
{
    /** @var integer */
    protected $wp_user_id;
    /** @var int */
    protected $category_id;
    /** @var integer */
    protected $attachment_id;
    /** @var string */
    protected $full_name;
    /** @var string */
    protected $email;
    /** @var string */
    protected $phone;
    /** @var string */
    protected $time_zone;
    /** @var string */
    protected $info;
    /** @var int */
    protected $working_time_limit;
    /** @var string */
    protected $visibility = 'public';
    /** @var int */
    protected $position;
    /** @var string */
    protected $google_data;
    /** @var string */
    protected $outlook_data;
    /** @var string */
    protected $apple_data;
    /** @var string */
    protected $zoom_authentication = 'default';
    /** @var string */
    protected $zoom_oauth_token;
    /** @var int */
    protected $icalendar = 0;
    /** @var string */
    protected $icalendar_token = '';
    /** @var int */
    protected $icalendar_days_before = 365;
    /** @var int */
    protected $icalendar_days_after = 365;
    /** @var string */
    protected $color;
    /** @var string */
    protected $gateways;

    protected static $table = 'bookly_staff';

    protected $loggable = true;

    protected static $schema = array(
        'id' => array( 'format' => '%d' ),
        'wp_user_id' => array( 'format' => '%d' ),
        'category_id' => array( 'format' => '%d', 'reference' => array( 'entity' => 'StaffCategory', 'namespace' => '\BooklyPro\Lib\Entities', 'required' => 'bookly-addon-pro' ) ),
        'attachment_id' => array( 'format' => '%d' ),
        'full_name' => array( 'format' => '%s' ),
        'email' => array( 'format' => '%s' ),
        'phone' => array( 'format' => '%s' ),
        'info' => array( 'format' => '%s' ),
        'working_time_limit' => array( 'format' => '%d' ),
        'visibility' => array( 'format' => '%s' ),
        'position' => array( 'format' => '%d', 'sequent' => true ),
        'google_data' => array( 'format' => '%s' ),
        'outlook_data' => array( 'format' => '%s' ),
        'apple_data' => array( 'format' => '%s' ),
        'time_zone' => array( 'format' => '%s' ),
        'zoom_authentication' => array( 'format' => '%s' ),
        'zoom_oauth_token' => array( 'format' => '%s' ),
        'icalendar' => array( 'format' => '%d' ),
        'icalendar_token' => array( 'format' => '%s' ),
        'icalendar_days_before' => array( 'format' => '%d' ),
        'icalendar_days_after' => array( 'format' => '%d' ),
        'color' => array( 'format' => '%s' ),
        'gateways' => array( 'format' => '%s' ),
    );

    /**
     * Get schedule items of staff member.
     *
     * @param int $location_id
     * @return StaffScheduleItem[]
     */
    public function getScheduleItems( $location_id = null )
    {
        $staff_id = $this->getId();
        $start_of_week = (int) get_option( 'start_of_week' );
        // Start of week affects the sorting.
        // If it is 0(Sun) then the result should be 1,2,3,4,5,6,7.
        // If it is 1(Mon) then the result should be 2,3,4,5,6,7,1.
        // If it is 2(Tue) then the result should be 3,4,5,6,7,1,2. Etc.
        $schedule = StaffScheduleItem::query()
            ->where( 'staff_id', $staff_id )
            ->sortBy( 'IF(r.day_index + 10 - ' . $start_of_week . ' > 10, r.day_index + 10 - ' . $start_of_week . ', 16 + r.day_index)' )
            ->indexBy( 'day_index' )
            ->where( 'location_id', $location_id )
            ->find();

        if ( count( $schedule ) === 7 ) {
            return $schedule;
        }

        $week = array( 1 => 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday' );
        foreach ( $week as $day_index => $day ) {
            $sch_item = new Lib\Entities\StaffScheduleItem();
            if ( ! $sch_item->loadBy( compact( 'day_index', 'staff_id', 'location_id' ) ) ) {
                $sch_item
                    ->setStaffId( $staff_id )
                    ->setLocationId( $location_id )
                    ->setDayIndex( $day_index )
                    ->setStartTime( get_option( 'bookly_bh_' . $day . '_start' ) ?: null )
                    ->setEndTime( get_option( 'bookly_bh_' . $day . '_end' ) ?: null )
                    ->save();
            }
        }

        return $this->getScheduleItems( $location_id );
    }

    /**
     * Get data for services associated with this staff member.
     *
     * @param $type
     * @return array
     */
    public function getServicesData( $type = Service::TYPE_SIMPLE )
    {
        $result = array();

        if ( $this->getId() ) {
            $query = StaffService::query( 'ss' )
                ->select( 'ss.*,
                    s.category_id,
                    s.title,
                    s.duration,
                    s.price AS service_price,
                    s.color,
                    s.online_meetings,
                    s.slot_length,
                    s.position,
                    c.name' )
                ->addSelect( sprintf( '%s AS service_capacity_min, %s AS service_capacity_max, %s AS units_min, %s AS units_max',
                    Lib\Proxy\Shared::prepareStatement( 1, 's.capacity_min', 'Service' ),
                    Lib\Proxy\Shared::prepareStatement( 1, 's.capacity_max', 'Service' ),
                    Lib\Proxy\Shared::prepareStatement( 1, 's.units_min', 'Service' ),
                    Lib\Proxy\Shared::prepareStatement( 1, 's.units_max', 'Service' )
                ) )
                ->leftJoin( 'Service', 's', 's.id = ss.service_id' )
                ->leftJoin( 'Category', 'c', 'c.id = s.category_id' )
                ->where( 'ss.staff_id', $this->getId() )
                ->where( 's.type', $type );
            if ( ! Lib\Proxy\Locations::servicesPerLocationAllowed() ) {
                $query->where( 'ss.location_id', null );
            }
            $staff_services = $query->sortBy( 'c.position, s.position' )->fetchArray();

            foreach ( $staff_services as $data ) {
                $staff_service = new StaffService( $data );

                $service = new Service();
                $service
                    ->setId( $data['service_id'] )
                    ->setTitle( $data['title'] )
                    ->setColor( $data['color'] )
                    ->setDuration( $data['duration'] )
                    ->setPrice( $data['service_price'] )
                    ->setUnitsMin( $data['units_min'] )
                    ->setUnitsMax( $data['units_max'] )
                    ->setPosition( $data['position'] )
                    ->setCapacityMin( $data['service_capacity_min'] )
                    ->setCapacityMax( $data['service_capacity_max'] )
                    ->setOnlineMeetings( $data['online_meetings'] )
                    ->setSlotLength( $data['slot_length'] );

                $category = new Category();
                $category
                    ->setId( $data['category_id'] )
                    ->setName( $data['name'] );

                $result[] = compact( 'staff_service', 'service', 'category' );
            }
        }

        return $result;
    }

    /**
     * Check whether staff is archived or not.
     *
     * @return bool
     */
    public function isArchived()
    {
        return $this->getVisibility() == 'archive';
    }

    /**
     * Check whether staff is on holiday on given day.
     *
     * @param \DateTime $day
     * @return bool
     */
    public function isOnHoliday( \DateTime $day )
    {
        $query = Holiday::query()
            ->whereRaw( '( DATE_FORMAT( date, %s ) = %s AND repeat_event = 1 ) OR date = %s', array( '%m-%d', $day->format( 'm-d' ), $day->format( 'Y-m-d' ) ) )
            ->where( 'staff_id', $this->getId() )
            ->limit( 1 );
        $rows = $query->execute();

        return $rows != 0;
    }

    /**
     * @param string $locale
     * @return string
     */
    public function getTranslatedName( $locale = null )
    {
        return Lib\Utils\Common::getTranslatedString( 'staff_' . $this->getId(), $this->getFullName(), $locale );
    }

    /**
     * @param string $locale
     * @return string
     */
    public function getTranslatedInfo( $locale = null )
    {
        return Lib\Utils\Common::getTranslatedString( 'staff_' . $this->getId() . '_info', $this->getInfo(), $locale );
    }

    /**
     * Get workload for given date.
     *
     * @param string $date 'Y-m-d'
     * @param array $exclude list of appointment id's to exclude
     * @return int
     */
    public function getWorkload( $date, $exclude = array() )
    {
        $start_date = $date . ' 00:00:00';
        $end_date = date_create( $date )->modify( '+1 day' )->format( 'Y-m-d H:i:s' );
        $appointments = Lib\Entities\CustomerAppointment::query( 'ca' )
            ->select( 'a.start_date, DATE_ADD(`a`.`end_date`, INTERVAL `a`.`extras_duration` SECOND) as end_date' )
            ->leftJoin( 'Appointment', 'a', 'a.id = ca.appointment_id' )
            ->where( 'a.staff_id', $this->getId() )
            ->whereLt( 'a.start_date', $end_date )
            ->whereGt( 'a.end_date', $start_date )
            ->whereIn( 'ca.status', Lib\Proxy\CustomStatuses::prepareBusyStatuses( array(
                Lib\Entities\CustomerAppointment::STATUS_PENDING,
                Lib\Entities\CustomerAppointment::STATUS_APPROVED,
                Lib\Entities\CustomerAppointment::STATUS_WAITLISTED,
            ) ) )
            ->whereNotIn( 'a.id', $exclude )
            ->fetchArray();

        $workload = 0;
        foreach ( $appointments as $appointment ) {
            $workload += min( strtotime( $end_date ), strtotime( $appointment['end_date'] ) ) - max( strtotime( $start_date ), strtotime( $appointment['start_date'] ) );
        }

        return $workload;
    }

    /**
     * Get holidays.
     *
     * @return array
     */
    public function getHolidays()
    {
        $collection = Lib\Entities\Holiday::query( 'h' )->where( 'h.staff_id', $this->getId() )->fetchArray();
        $holidays = array();
        foreach ( $collection as $holiday ) {
            list ( $Y, $m, $d ) = explode( '-', $holiday['date'] );
            $holidays[ $holiday['id'] ] = array(
                'm' => (int) $m,
                'd' => (int) $d,
            );
            // if not repeated holiday, add the year
            if ( ! $holiday['repeat_event'] ) {
                $holidays[ $holiday['id'] ]['y'] = (int) $Y;
            }
        }

        return $holidays;
    }

    /**
     * Get staff image url
     *
     * @param string $size
     *
     * @return string
     */
    public function getImageUrl( $size = 'full' )
    {
        return $this->attachment_id
            ? Lib\Utils\Common::getAttachmentUrl( $this->attachment_id, $size )
            : '';
    }

    /**************************************************************************
     * Entity Fields Getters & Setters                                        *
     **************************************************************************/

    /**
     * Gets wp_user_id
     *
     * @return int
     */
    public function getWpUserId()
    {
        return $this->wp_user_id;
    }

    /**
     * Sets wp_user_id
     *
     * @param int $wp_user_id
     * @return $this
     */
    public function setWpUserId( $wp_user_id )
    {
        $this->wp_user_id = $wp_user_id;

        return $this;
    }

    /**
     * Gets category_id
     *
     * @return int
     */
    public function getCategoryId()
    {
        return $this->category_id;
    }

    /**
     * Sets category_id
     *
     * @param int $category_id
     * @return $this
     */
    public function setCategoryId( $category_id )
    {
        $this->category_id = $category_id;

        return $this;
    }

    /**
     * Gets attachment_id
     *
     * @return int
     */
    public function getAttachmentId()
    {
        return $this->attachment_id;
    }

    /**
     * Sets attachment_id
     *
     * @param int $attachment_id
     * @return $this
     */
    public function setAttachmentId( $attachment_id )
    {
        $this->attachment_id = $attachment_id;

        return $this;
    }

    /**
     * Gets full name
     *
     * @return string
     */
    public function getFullName()
    {
        return $this->full_name;
    }

    /**
     * Sets full name
     *
     * @param string $full_name
     * @return $this
     */
    public function setFullName( $full_name )
    {
        $this->full_name = $full_name;

        return $this;
    }

    /**
     * Gets email
     *
     * @return string
     */
    public function getEmail()
    {
        return $this->email;
    }

    /**
     * Sets email
     *
     * @param string $email
     * @return $this
     */
    public function setEmail( $email )
    {
        $this->email = $email;

        return $this;
    }

    /**
     * Gets phone
     *
     * @return string
     */
    public function getPhone()
    {
        return $this->phone;
    }

    /**
     * Sets phone
     *
     * @param string $phone
     * @return $this
     */
    public function setPhone( $phone )
    {
        $this->phone = $phone;

        return $this;
    }

    /**
     * Gets time_zone
     *
     * @param bool $convert_utc_values Whether to convert values like UTC+1 into +01:00
     * @return string
     */
    public function getTimeZone( $convert_utc_values = true )
    {
        if ( $convert_utc_values && $this->time_zone && preg_match( '/^UTC[+-]/', $this->time_zone ) ) {
            $offset = preg_replace( '/UTC\+?/', '', $this->time_zone );

            return Lib\Utils\DateTime::formatOffset( $offset * HOUR_IN_SECONDS );
        }

        return $this->time_zone;
    }

    /**
     * Sets time_zone
     *
     * @param string $time_zone
     * @return $this
     */
    public function setTimeZone( $time_zone )
    {
        $this->time_zone = $time_zone;

        return $this;
    }

    /**
     * Gets info
     *
     * @return string
     */
    public function getInfo()
    {
        return $this->info;
    }

    /**
     * Sets info
     *
     * @param string $info
     * @return $this
     */
    public function setInfo( $info )
    {
        $this->info = $info;

        return $this;
    }

    /**
     * Gets working_time_limit
     *
     * @return int
     */
    public function getWorkingTimeLimit()
    {
        return $this->working_time_limit;
    }

    /**
     * Sets working_time_limit
     *
     * @param int $working_time_limit
     * @return $this
     */
    public function setWorkingTimeLimit( $working_time_limit )
    {
        $this->working_time_limit = $working_time_limit;

        return $this;
    }

    /**
     * Gets visibility
     *
     * @return string
     */
    public function getVisibility()
    {
        return $this->visibility;
    }

    /**
     * Sets visibility
     *
     * @param string $visibility
     * @return $this
     */
    public function setVisibility( $visibility )
    {
        $this->visibility = $visibility;

        return $this;
    }

    /**
     * Gets position
     *
     * @return int
     */
    public function getPosition()
    {
        return $this->position;
    }

    /**
     * Sets position
     *
     * @param int $position
     * @return $this
     */
    public function setPosition( $position )
    {
        $this->position = $position;

        return $this;
    }

    /**
     * Gets Google data
     *
     * @return string
     */
    public function getGoogleData()
    {
        return $this->google_data;
    }

    /**
     * Sets Google data
     *
     * @param string $google_data
     * @return $this
     */
    public function setGoogleData( $google_data )
    {
        $this->google_data = $google_data;

        return $this;
    }

    /**
     * Gets Microsoft data
     *
     * @return string
     */
    public function getOutlookData()
    {
        return $this->outlook_data;
    }

    /**
     * Sets Microsoft data
     *
     * @param string $outlook_data
     * @return $this
     */
    public function setOutlookData( $outlook_data )
    {
        $this->outlook_data = $outlook_data;

        return $this;
    }

    /**
     * Gets Apple Calendar data
     *
     * @return string
     */
    public function getAppleData()
    {
        return $this->apple_data;
    }

    /**
     * Sets Apple Calendar data
     *
     * @param string $apple_data
     * @return $this
     */
    public function setAppleData( $apple_data )
    {
        $this->apple_data = $apple_data;

        return $this;
    }

    /**
     * Gets zoom_authentication
     *
     * @return string
     */
    public function getZoomAuthentication()
    {
        return $this->zoom_authentication;
    }

    /**
     * Sets zoom_authentication
     *
     * @param string $zoom_authentication
     * @return $this
     */
    public function setZoomAuthentication( $zoom_authentication )
    {
        $this->zoom_authentication = $zoom_authentication;

        return $this;
    }

    /**
     * Gets zoom_oauth_token
     *
     * @return string
     */
    public function getZoomOAuthToken()
    {
        return $this->zoom_oauth_token;
    }

    /**
     * Sets zoom_oauth_token
     *
     * @param string $zoom_oauth_token
     * @return $this
     */
    public function setZoomOAuthToken( $zoom_oauth_token )
    {
        $this->zoom_oauth_token = $zoom_oauth_token;

        return $this;
    }

    /**
     * Gets icalendar
     *
     * @return int
     */
    public function getICalendar()
    {
        return $this->icalendar;
    }

    /**
     * Sets icalendar
     *
     * @param int $icalendar
     * @return $this
     */
    public function setICalendar( $icalendar )
    {
        $this->icalendar = $icalendar;

        return $this;
    }

    /**
     * Gets icalendar_token
     *
     * @return string
     */
    public function getICalendarToken()
    {
        return $this->icalendar_token;
    }

    /**
     * Sets icalendar_token
     *
     * @param string $icalendar_token
     * @return $this
     */
    public function setICalendarToken( $icalendar_token )
    {
        $this->icalendar_token = $icalendar_token;

        return $this;
    }

    /**
     * Gets icalendar_days_before
     *
     * @return string
     */
    public function getICalendarDaysBefore()
    {
        return $this->icalendar_days_before;
    }

    /**
     * Sets icalendar_days_before
     *
     * @param string $icalendar_days_before
     * @return $this
     */
    public function setICalendarDaysBefore( $icalendar_days_before )
    {
        $this->icalendar_days_before = $icalendar_days_before;

        return $this;
    }

    /**
     * Gets icalendar_days_after
     *
     * @return string
     */
    public function getICalendarDaysAfter()
    {
        return $this->icalendar_days_after;
    }

    /**
     * Sets icalendar_days_after
     *
     * @param string $icalendar_days_after
     * @return $this
     */
    public function setICalendarDaysAfter( $icalendar_days_after )
    {
        $this->icalendar_days_after = $icalendar_days_after;

        return $this;
    }

    /**
     * Gets color
     *
     * @return string
     */
    public function getColor()
    {
        return $this->color;
    }

    /**
     * Sets color
     *
     * @param string $color
     * @return $this
     */
    public function setColor( $color )
    {
        $this->color = $color;

        return $this;
    }

    /**
     * Gets gateways
     *
     * @return string
     */
    public function getGateways()
    {
        return $this->gateways;
    }

    /**
     * Sets gateways
     *
     * @param string $gateways
     * @return $this
     */
    public function setGateways( $gateways )
    {
        $this->gateways = $gateways;

        return $this;
    }

    /**************************************************************************
     * Overridden Methods                                                     *
     **************************************************************************/

    /**
     * Delete staff member.
     */
    public function delete()
    {
        Lib\Proxy\Pro::revokeGoogleCalendarToken( $this );
        $auths = Auth::query()->where( 'staff_id', $this->getId() )->fetchCol( 'token' );
        if ( $auths ) {
            $cloud = Lib\Cloud\API::getInstance();
            $cloud->getProduct( Lib\Cloud\Account::PRODUCT_MOBILE_STAFF_CABINET )->revokeKeys( $auths );
        }

        /** @var Appointment[] $app_list */
        $app_list = Appointment::query()->where( 'staff_id', $this->getId() )->find();
        foreach ( $app_list as $app ) {
            $app->delete();
        }

        parent::delete();
    }

    /**
     * @return false|int
     */
    public function save()
    {
        $is_new = ! $this->getId();

        if ( $is_new && $this->getWpUserId() ) {
            $user = get_user_by( 'id', $this->getWpUserId() );
            if ( $user ) {
                $this->setEmail( $user->get( 'user_email' ) );
            }
        }
        if ( $this->icalendar_token == '' ) {
            $this->setICalendarToken( Lib\Utils\Common::generateToken( get_class( $this ), 'icalendar_token' ) );
        }

        if ( $this->color === null ) {
            $this->color = sprintf( '#%06X', mt_rand( 0, 0x64FFFF ) );
        }

        $saved = parent::save();

        if ( $saved ) {
            // Register string for translate in WPML.
            do_action( 'wpml_register_single_string', 'bookly', 'staff_' . $this->getId(), $this->getFullName() );
            do_action( 'wpml_register_single_string', 'bookly', 'staff_' . $this->getId() . '_info', $this->getInfo() );

            if ( $is_new ) {
                // Schedule items.
                $staff_id = $this->getId();
                foreach ( Lib\Config::getBusinessHours() as $day_index => $bh ) {
                    $item = new StaffScheduleItem();
                    $item
                        ->setStaffId( $staff_id )
                        ->setDayIndex( $day_index + 1 )
                        ->setStartTime( $bh['start'] )
                        ->setEndTime( $bh['end'] )
                        ->save();
                }

                // Create holidays for staff
                self::$wpdb->query( sprintf(
                    'INSERT INTO `' . Holiday::getTableName() . '` (`parent_id`, `staff_id`, `date`, `repeat_event`)
                SELECT `id`, %d, `date`, `repeat_event` FROM `' . Holiday::getTableName() . '` WHERE `staff_id` IS NULL',
                    $staff_id
                ) );
            }
        }

        return $saved;
    }

}