<?php

namespace onespace\tools\activeApi\models\oneaccess;

use DateTime;
use onespace\tools\activeApi\components\clients\OneAccessClient;
use onespace\tools\activeApi\helpers\BitwiseHelperTrait;
use yii\helpers\ArrayHelper;
use yii\web\NotFoundHttpException;

/**
 * This class is intended to be extended from within the child service,
 * though it shouldn't need to be if you just need a basic interface
 * with the method / endpoint without any extra methods.
 *
 * @property integer $id
 * @property string $guid
 * @property string $site_id
 * @property integer $right_type
 * @property string $name
 * @property string $description
 * @property integer $visitor
 * @property integer $days_of_week
 * @property string $times_of_day
 *
 * @author  Gareth Palmer <gareth@one-space.co.za>
 */

class AccessRight extends \onespace\tools\activeApi\models\BaseActiveApiModel {
    use BitwiseHelperTrait;

    public $id;
    public $guid;
    public $site_id;
    public $right_type;
    public $name;
    public $description;
    public $visitor;
    public $days_of_week;
    public $times_of_day;


    public $daysList;
    public $timesList;

    public const GROUP_TYPE_NOT_BUILT_IN = 0;
    public const GROUP_TYPE_DEFAULT_RESIDENT = 1;
    public const GROUP_TYPE_DEFAULT_VISITOR = 2;

    public const DAY_ALL       = 0b00000000;
    public const DAY_MONDAY    = 0b00000001;
    public const DAY_TUESDAY   = 0b00000010;
    public const DAY_WEDNESDAY = 0b00000100;
    public const DAY_THURSDAY  = 0b00001000;
    public const DAY_FRIDAY    = 0b00010000;
    public const DAY_SATURDAY  = 0b00100000;
    public const DAY_SUNDAY    = 0b01000000;
    public const DAY_HOLIDAYS  = 0b10000000;

    public const DAYS_NO_LIMIT = 0b11111111;


    public const TIME_ALL                            = 0b0;
    public const TIME_MIDNIGHT_ONE_UCT               = 0b1;
    public const TIME_MIDNIGHT_ONE_HALF_UCT          = 0b10;
    public const TIME_ONE_TWO_UCT                    = 0b100;
    public const TIME_ONE_TWO_HALF_UCT               = 0b1000;
    public const TIME_TWO_THREE_UCT                  = 0b10000;
    public const TIME_TWO_THREE_HALF_UCT             = 0b100000;
    public const TIME_THREE_FOUR_UCT                 = 0b1000000;
    public const TIME_THREE_FOUR_HALF_UCT            = 0b10000000;
    public const TIME_FOUR_FIVE_UCT                  = 0b100000000;
    public const TIME_FOUR_FIVE_HALF_UCT             = 0b1000000000;
    public const TIME_FIVE_SIX_UCT                   = 0b10000000000;
    public const TIME_FIVE_SIX_HALF_UCT              = 0b100000000000;
    public const TIME_SIX_SEVEN_UCT                  = 0b1000000000000;
    public const TIME_SIX_SEVEN_HALF_UCT             = 0b10000000000000;
    public const TIME_SEVEN_EIGHT_UCT                = 0b100000000000000;
    public const TIME_SEVEN_EIGHT_HALF_UCT           = 0b1000000000000000;
    public const TIME_EIGHT_NINE_UCT                 = 0b10000000000000000;
    public const TIME_EIGHT_NINE_HALF_UCT            = 0b100000000000000000;
    public const TIME_NINE_TEN_UCT                   = 0b1000000000000000000;
    public const TIME_NINE_TEN_HALF_UCT              = 0b10000000000000000000;
    public const TIME_TEN_ELEVEN_UCT                 = 0b100000000000000000000;
    public const TIME_TEN_ELEVEN_HALF_UCT            = 0b1000000000000000000000;
    public const TIME_ELEVEN_TWELVE_UCT              = 0b10000000000000000000000;
    public const TIME_ELEVEN_TWELVE_HALF_UCT         = 0b100000000000000000000000;
    public const TIME_TWELVE_THIRTEEN_UCT            = 0b1000000000000000000000000;
    public const TIME_TWELVE_THIRTEEN_HALF_UCT       = 0b10000000000000000000000000;
    public const TIME_THIRTEEN_FOURTEEN_UCT          = 0b100000000000000000000000000;
    public const TIME_THIRTEEN_FOURTEEN_HALF_UCT     = 0b1000000000000000000000000000;
    public const TIME_FOURTEEN_FIFTEEN_UCT           = 0b10000000000000000000000000000;
    public const TIME_FOURTEEN_FIFTEEN_HALF_UCT      = 0b100000000000000000000000000000;
    public const TIME_FIFTEEN_SIXTEEN_UCT            = 0b1000000000000000000000000000000;
    public const TIME_FIFTEEN_SIXTEEN_HALF_UCT       = 0b10000000000000000000000000000000;
    public const TIME_SIXTEEN_SEVENTEEN_UCT          = 0b100000000000000000000000000000000;
    public const TIME_SIXTEEN_SEVENTEEN_HALF_UCT     = 0b1000000000000000000000000000000000;
    public const TIME_SEVENTEEN_EIGHTEEN_UCT         = 0b10000000000000000000000000000000000;
    public const TIME_SEVENTEEN_EIGHTEEN_HALF_UCT    = 0b100000000000000000000000000000000000;
    public const TIME_EIGHTEEN_NINETEEN_UCT          = 0b1000000000000000000000000000000000000;
    public const TIME_EIGHTEEN_NINETEEN_HALF_UCT     = 0b10000000000000000000000000000000000000;
    public const TIME_NINETEEN_TWENTY_UCT            = 0b100000000000000000000000000000000000000;
    public const TIME_NINETEEN_TWENTY_HALF_UCT       = 0b1000000000000000000000000000000000000000;
    public const TIME_TWENTY_TWENTYONE_UCT           = 0b10000000000000000000000000000000000000000;
    public const TIME_TWENTY_TWENTYONE_HALF_UCT      = 0b100000000000000000000000000000000000000000;
    public const TIME_TWENTYONE_TWENTYTWO_UCT        = 0b1000000000000000000000000000000000000000000;
    public const TIME_TWENTYONE_TWENTYTWO_HALF_UCT   = 0b10000000000000000000000000000000000000000000;
    public const TIME_TWENTYTWO_TWENTYTHREE_UCT      = 0b100000000000000000000000000000000000000000000;
    public const TIME_TWENTYTWO_TWENTYTHREE_HALF_UCT = 0b1000000000000000000000000000000000000000000000;
    public const TIME_TWENTYTHREE_MIDNIGHT_UCT       = 0b10000000000000000000000000000000000000000000000;
    public const TIME_TWENTYTHREE_MIDNIGHT_HALF_UCT  = 0b100000000000000000000000000000000000000000000000;

    public const TIME_NO_LIMIT = 0b111111111111111111111111111111111111111111111111;

    public const TIMES_UCT = [
        self::TIME_MIDNIGHT_ONE_UCT,
        self::TIME_MIDNIGHT_ONE_HALF_UCT,
        self::TIME_ONE_TWO_UCT,
        self::TIME_ONE_TWO_HALF_UCT,
        self::TIME_TWO_THREE_UCT,
        self::TIME_TWO_THREE_HALF_UCT,
        self::TIME_THREE_FOUR_UCT,
        self::TIME_THREE_FOUR_HALF_UCT,
        self::TIME_FOUR_FIVE_UCT,
        self::TIME_FOUR_FIVE_HALF_UCT,
        self::TIME_FIVE_SIX_UCT,
        self::TIME_FIVE_SIX_HALF_UCT,
        self::TIME_SIX_SEVEN_UCT,
        self::TIME_SIX_SEVEN_HALF_UCT,
        self::TIME_SEVEN_EIGHT_UCT,
        self::TIME_SEVEN_EIGHT_HALF_UCT,
        self::TIME_EIGHT_NINE_UCT,
        self::TIME_EIGHT_NINE_HALF_UCT,
        self::TIME_NINE_TEN_UCT,
        self::TIME_NINE_TEN_HALF_UCT,
        self::TIME_TEN_ELEVEN_UCT,
        self::TIME_TEN_ELEVEN_HALF_UCT,
        self::TIME_ELEVEN_TWELVE_UCT,
        self::TIME_ELEVEN_TWELVE_HALF_UCT,
        self::TIME_TWELVE_THIRTEEN_UCT,
        self::TIME_TWELVE_THIRTEEN_HALF_UCT,
        self::TIME_THIRTEEN_FOURTEEN_UCT,
        self::TIME_THIRTEEN_FOURTEEN_HALF_UCT,
        self::TIME_FOURTEEN_FIFTEEN_UCT,
        self::TIME_FOURTEEN_FIFTEEN_HALF_UCT,
        self::TIME_FIFTEEN_SIXTEEN_UCT,
        self::TIME_FIFTEEN_SIXTEEN_HALF_UCT,
        self::TIME_SIXTEEN_SEVENTEEN_UCT,
        self::TIME_SIXTEEN_SEVENTEEN_HALF_UCT,
        self::TIME_SEVENTEEN_EIGHTEEN_UCT,
        self::TIME_SEVENTEEN_EIGHTEEN_HALF_UCT,
        self::TIME_EIGHTEEN_NINETEEN_UCT,
        self::TIME_EIGHTEEN_NINETEEN_HALF_UCT,
        self::TIME_NINETEEN_TWENTY_UCT,
        self::TIME_NINETEEN_TWENTY_HALF_UCT,
        self::TIME_TWENTY_TWENTYONE_UCT,
        self::TIME_TWENTY_TWENTYONE_HALF_UCT,
        self::TIME_TWENTYONE_TWENTYTWO_UCT,
        self::TIME_TWENTYONE_TWENTYTWO_HALF_UCT,
        self::TIME_TWENTYTWO_TWENTYTHREE_UCT,
        self::TIME_TWENTYTWO_TWENTYTHREE_HALF_UCT,
        self::TIME_TWENTYTHREE_MIDNIGHT_UCT,
        self::TIME_TWENTYTHREE_MIDNIGHT_HALF_UCT,
    ];

    public const TIME_LABELS = [
        '00:00 - 00:30',
        '00:30 - 01:00',
        '01:00 - 01:30',
        '01:30 - 02:00',
        '02:00 - 02:30',
        '02:30 - 03:00',
        '03:00 - 03:30',
        '03:30 - 04:00',
        '04:00 - 04:30',
        '04:30 - 05:00',
        '05:00 - 05:30',
        '05:30 - 06:00',
        '06:00 - 06:30',
        '06:30 - 07:00',
        '07:00 - 07:30',
        '07:30 - 08:00',
        '08:00 - 08:30',
        '08:30 - 09:00',
        '09:00 - 09:30',
        '09:30 - 10:00',
        '10:00 - 10:30',
        '10:30 - 11:00',
        '11:00 - 11:30',
        '11:30 - 12:00',
        '12:00 - 12:30',
        '12:30 - 13:00',
        '13:00 - 13:30',
        '13:30 - 14:00',
        '14:00 - 14:30',
        '14:30 - 15:00',
        '15:00 - 15:30',
        '15:30 - 16:00',
        '16:00 - 16:30',
        '16:30 - 17:00',
        '17:00 - 17:30',
        '17:30 - 18:00',
        '18:00 - 18:30',
        '18:30 - 19:00',
        '19:00 - 19:30',
        '29:30 - 20:00',
        '20:00 - 20:30',
        '20:30 - 21:00',
        '21:00 - 21:30',
        '21:30 - 22:00',
        '22:00 - 22:30',
        '22:30 - 23:00',
        '23:00 - 23:30',
        '23:30 - 00:00',
    ];

    /**
     * Returns a `[$dayINTid => $name]` array of all possible days.
     *
     * @static
     */
    public static function days(): array {
        return [
            self::DAY_SUNDAY => 'Sunday',
            self::DAY_MONDAY => 'Monday',
            self::DAY_TUESDAY => 'Tuesday',
            self::DAY_WEDNESDAY => 'Wednesday',
            self::DAY_THURSDAY => 'Thursday',
            self::DAY_FRIDAY => 'Friday',
            self::DAY_SATURDAY => 'Saturday',
            self::DAY_HOLIDAYS => 'Holidays',
        ];
    }

    /**
     * Returns a `[$timeINTid => $name]` array of all possible times.
     *
     * @static
     */
    public static function times(): array {
        // ⚠️ as yet untested

        // Get timezone offset in seconds and convert to hours
        $offsetSeconds = (new DateTime())->getOffset();
        $offsetHours = $offsetSeconds / 3600;

        // Calculate the shift index based on 30-minute intervals (2 per hour)
        $shift = ($offsetHours * 2) % count(self::TIMES_UCT);

        // Reorder the array to start from local midnight
        $sortedTimes = array_values(array_merge(array_slice(self::TIMES_UCT, $shift), array_slice(self::TIMES_UCT, 0, $shift)));

        $timesList = [];
        foreach ($sortedTimes as $i => $sortedTime) {
            $timesList[$sortedTime] = self::TIME_LABELS[$i];
        }

        return $timesList;
    }

    /**
     * {@inheritdoc}
     */
    public function rules() {
        return ArrayHelper::merge(parent::rules(), [
            [['description', 'visitor'], 'default', 'value' => null],
            [['times_of_day'], 'default', 'value' => 0],
            [['id', 'right_type', 'visitor', 'days_of_week', 'times_of_day'], 'integer'],
            [['guid', 'site_id'], 'string', 'max' => 36],
            [['name', 'description'], 'string', 'max' => 255],
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public function attributeLabels() {
        return [
            'id' => 'ID',
            'guid' => 'Guid',
            'site_id' => 'Estate',
            'name' => 'Name',
            'description' => 'Description',
            'visitor' => 'Visitor',
            'days_of_week' => 'Days Of Week',
            'times_of_day' => 'Times Of Day',
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function client(): OneAccessClient {
        return new OneAccessClient();
    }

    /**
     * {@inheritdoc}
     */
    public function primaryKey(): string {
        return 'id';
    }

    /**
     * {@inheritdoc}
     */
    public function baseEndpoints(): array {
        return [
            'v1' => 'v1/access-right',
            'v2' => 'v2/access-right',
        ];
    }

    /**
     * @throws NotFoundHttpException
     */
    public static function findById(int $id): static {
        $model = self::findOne(['id' => $id]);
        if ($model === null) {
            throw new NotFoundHttpException("Right not found");
        }
        return $model;
    }

    /**
     * @throws NotFoundHttpException
     */
    public static function findByGuid(string $guid): static {
        $model = self::findOne(['guid' => $guid]);
        if ($model === null) {
            throw new NotFoundHttpException("Right not found");
        }
        return $model;
    }

    /**
     * Whether or not a specified day is enabled.
     *
     * Should be passed as something like `AccessRight::DAY_WEDNESDAY`.
     */
    public function dayEnabled(int $day): bool {
        return $this->bitwiseHasValue('days_of_week', $day);
    }

    /**
     * Whether or not a specified time is enabled.
     *
     * Should be passed as something like `AccessRight::TIME_EIGHT_NINE`.
     */
    public function timeEnabled(int $time): bool {
        return $this->bitwiseHasValue('times_of_day', $time);
    }

    /**
     * Returns a list of days that are enabled on this group model.
     *
     * `[$dayINTid => $name]`
     */
    public function getActiveDays(): array {
        $active = [];
        foreach ($this->days() as $day => $name) {
            if ($this->dayEnabled($day)) {
                $active[$day] = $name;
            }
        }
        return $active;
    }

    /**
     * Returns a list of times that are enabled on this group model.
     *
     * `[$timeINTid => $name]`
     */
    public function getActiveTimes(): array {
        $active = [];
        foreach ($this->times() as $time => $name) {
            if ($this->timeEnabled($time)) {
                $active[$time] = $name;
            }
        }
        return $active;
    }

    /**
     * Sets the `days_of_week` int bitwise property from the array property `daysList`.
     */
    public function setDaysOfWeekFromData(): void {
        $days = empty($this->daysList) ? [] : $this->daysList;
        $this->days_of_week = array_reduce($days, fn ($carry, $day) => $carry | $day, 0);
    }

    /**
     * Sets the `time_of_day` int bitwise property from the array property `timesList`.
     */
    public function setTimesOfDayFromData(): void {
        $times = empty($this->timesList) ? [] : $this->timesList;
        $this->times_of_day = array_reduce($times, fn ($carry, $time) => $carry | $time, 0);
    }

    /**
     * Sets the `daysList` array property from the bitwise int `days_of_week` property.
     */
    public function setDataFromDaysOfWeek(): void {
        $this->daysList = [];
        foreach ($this->days() as $day => $name) {
            if ($this->bitwiseHasValue('days_of_week', $day)) {
                $this->daysList[] = $day;
            }
        }
    }

    /**
     * Sets the `timesList` array property from the bitwise int `times_of_day` property.
     */
    public function setDataFromTimesOfDay(): void {
        $this->timesList = [];
        foreach ($this->times() as $time => $name) {
            if ($this->bitwiseHasValue('times_of_day', $time)) {
                $this->timesList[] = $time;
            }
        }
    }
}
