<?php

namespace Booktics\Services;

use Booktics\Abstracts\Booktics_Database;
use Booktics\Contracts\Database_Interface;
use DateTime;
use Exception;
use InvalidArgumentException;

/**
 * Schedule Manager for team member and service
 *
 * @package Booktics/Services
 */
class Schedule_Manager {

    /**
     * Store Team member id
     *
     * @var int
     */
    private $team_member_id = 0;

    /**
     * Store Service id
     *
     * @var int
     */
    private $service_id = 0;

    /**
     * Store duration of each slot
     *
     * @var int
     */
    private $duration = 30;

    /**
     * Store schedules map
     *
     * @var array
     */
    private $schedules = array();

    /**
     * Store custom schedule map
     *
     * @var array
     */
    private $custom_schedules = array();

    /**
     * Store holidays map
     *
     * @var array
     */
    private $holidays = array();

    /**
     * Week day map
     *
     * @var array
     */
    public static $day_map = array(
        'Mon' => 1,
        'Tue' => 2,
        'Wed' => 3,
        'Thu' => 4,
        'Fri' => 5,
        'Sat' => 6,
        'Sun' => 7,
    );

    /**
     * WP Database instance
     *
     * @var Database_Interface
     */
    private $database;

    /**
     * Schedule data
     *
     * @var array
     */
    private $schedule_data;

    /**
     * Construct object and set schedule data
     *
     * @param $schedule_data
     */
    private function __construct( $schedule_data ) {
        $this->database      = new Booktics_Database();
        $this->schedule_data = $schedule_data;
        if ( ! isset( $this->schedule_data['enable_custom_holiday'] ) || ! $this->schedule_data['enable_custom_holiday'] ) {
            $this->schedule_data['holiday'] = array();
        }
        if ( ! isset( $this->schedule_data['enable_custom_schedule'] ) || ! $this->schedule_data['enable_custom_schedule'] ) {
            $this->schedule_data['custom'] = array();
        }
        $this->init(
            $this->schedule_data['schedule'] ?? array(),
            $this->schedule_data['custom'] ?? array(),
            $this->schedule_data['holiday'] ?? array()
        );
    }

    /**
     * Make a schedule manager instance
     *
     * @param $schedule_data
     *
     * @return self
     */
    public static function make( $schedule_data ): self {
        return new static( $schedule_data );
    }

    /**
     * Initialize schedules, Custom Schedules and holidays
     *
     * @param $schedules
     * @param $custom_schedules
     * @param $holidays
     *
     * @return void
     */
    private function init( $schedules, $custom_schedules, $holidays ): void {
        $this->schedules = $schedules;

        if ( ! empty( $custom_schedules ) ) {
            foreach ( $custom_schedules as $custom_data ) {
                if ( ! is_array( $custom_data ) || count( $custom_data ) < 2 ) {
                    continue;
                }
                $start = $custom_data[0];
                $end   = $custom_data[1];
                if ( ! isset( $custom_data[2] ) ) {
                    $end         = $start;
                    $time_period = $custom_data[1];
                } else {
                    $time_period = $custom_data[2];
                }

                $this->add_custom_schedule( $start, $end, $time_period );
            }
        }

        if ( ! empty( $holidays ) ) {
            foreach ( $holidays as $holiday_data ) {
                if ( ! is_array( $holiday_data ) || empty( $holiday_data ) ) {
                    continue;
                }
                $start = $holiday_data[0];
                $end   = $holiday_data[1] ?? $holiday_data[0];

                $this->with_holidays( $start, $end );
            }
        }
    }

    /**
     * Set team member id for team member schedule
     *
     * @param int $team_member_id
     *
     * @return $this
     */
    public function with_team_member_id( int $team_member_id ): self {
        if ( $team_member_id <= 0 ) {
            throw new InvalidArgumentException( 'Team member ID must be positive' );
        }
        $this->team_member_id = $team_member_id;

        return $this;
    }

    /**
     * Set service id for service schedule
     *
     * @param int $service_id
     *
     * @return $this
     */
    public function with_service_id( int $service_id ): self {
        if ( $service_id <= 0 ) {
            throw new InvalidArgumentException( 'Service ID must be positive' );
        }
        $this->service_id = $service_id;

        return $this;
    }

    /**
     * Set schedule map to store
     *
     * @param array $schedules
     *
     * @return $this
     */
    public function with_schedules( array $schedules ): self {
        $this->schedules = $schedules;

        return $this;
    }

    /**
     * Set holidays map to store
     *
     * @param string $start_date
     * @param string $end_date
     *
     * @return $this
     */
    public function with_holidays( string $start_date, string $end_date ): self {
        $this->holidays[] = array(
            'start_date' => $start_date,
            'end_date'   => $end_date,
        );

        return $this;
    }

    /**
     * Set a custom schedule map to store
     *
     * @param string $start_date
     * @param string $end_date
     * @param array $schedule
     *
     * @return $this
     */
    public function add_custom_schedule( string $start_date, string $end_date, array $schedule ): self {
        $this->custom_schedules[] = array(
            'start_date' => $start_date,
            'end_date'   => $end_date,
            'schedule'   => $schedule,
        );

        return $this;
    }

    /**
     * Execute schedule save action
     *
     * @return void
     * @throws Exception
     */
    public function save(): void {
        $this->database->begin_transaction();
        try {
            $this->clear_existing_schedules();
            $this->save_schedules();
            $this->database->commit();
        } catch ( Exception $e ) {
            $this->database->roll_back();
            throw $e;
        }
    }

    /**
     * Remove existing schedules from database
     *
     * @return void
     */
    private function clear_existing_schedules(): void {
        $this->database->delete(
            booktics_get_table_name( 'booktics_schedules' ), array(
				'service_id'     => $this->service_id,
				'team_member_id' => $this->team_member_id,
            )
        );
    }

    /**
     * Insert Schedule into database
     *
     * @return void
     * @throws Exception
     */
    private function save_schedules(): void {
        $records = $this->build_schedule_records();

        if ( empty( $records ) ) {
            return;
        }

        $this->database->insert_batch( booktics_get_table_name( 'booktics_schedules' ), $records );
    }

    /**
     * build map for schedule records
     *
     * @return array
     * @throws Exception
     */
    private function build_schedule_records(): array {
        $records = array();

        foreach ( $this->schedules as $day_name => $day_values ) {
            $formatted_day_name = ucfirst( substr( strtolower( $day_name ), 0, 3 ) );
            $day_number         = static::$day_map[ $formatted_day_name ];
            foreach ( $day_values as $day_value ) {
                $records[] = $this->create_schedule_record(
                    $day_value['start_time'] ?? '0',
                    $day_value['end_time'] ?? '0',
                    $day_number,
                    0
                );
            }
        }

        if ( count( $this->custom_schedules ) > 0 ) {
            foreach ( $this->custom_schedules as $custom_schedule ) {
                $dates = booktics_date_range_loop( $custom_schedule['start_date'], $custom_schedule['end_date'] );
                foreach ( $dates as $date ) {
                    $day_name   = ( new DateTime( $date ) )->format( 'D' );
                    $day_number = static::$day_map[ $day_name ];
                    $records[]  = $this->create_schedule_record(
                        $custom_schedule['schedule']['start_time'],
                        $custom_schedule['schedule']['end_time'],
                        $day_number,
                        $date
                    );
                }
            }
        }

        foreach ( $this->holidays as $holiday ) {
            $dates = booktics_date_range_loop( $holiday['start_date'], $holiday['end_date'] );
            foreach ( $dates as $date ) {
                $day_name   = ( new DateTime( $date ) )->format( 'D' );
                $day_number = static::$day_map[ $day_name ];
                $records[]  = $this->create_schedule_record(
                    '0',
                    '0',
                    $day_number,
                    $date
                );
            }
        }

        return $records;
    }

    /**
     * Format schedule records
     *
     * @param string $start_time
     * @param string $end_time
     * @param string $week_day
     * @param string $custom_date
     *
     * @return array
     */
    private function create_schedule_record(
        string $start_time,
        string $end_time,
        string $week_day,
        string $custom_date
    ): array {
        $utc_start_time = booktics_time_to_gmt( $start_time, $custom_date );
        $utc_end_time   = booktics_time_to_gmt( $end_time, $custom_date );

        return array(
            'team_member_id' => $this->team_member_id,
            'service_id'     => $this->service_id,
            'start_time'     => $utc_start_time,
            'end_time'       => $utc_end_time,
            'week_day'       => $week_day,
            'custom_date'    => $custom_date,
        );
    }
}
