<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;
use Illuminate\Contracts\Validation\DataAwareRule;
use Illuminate\Support\Str;

class FormPropertyLogicRule implements Rule, DataAwareRule 
{

    const ACTIONS_VALUES = [
        'show-block',
        'hide-block',
        'make-it-optional',
        'require-answer',
        'enable-block',
        'disable-block'
    ];

    const CONDITION_MAPPING = [
        'text' => [
            'comparators' => [
                'equals' => [
                    'expected_type' => 'string',
                ],
                'does_not_equal' => [
                    'expected_type' => 'string',
                ],
                'contains' => [
                    'expected_type' => 'string',
                ],
                'does_not_contain' => [
                    'expected_type' => 'string',
                ],
                'starts_with' => [
                    'expected_type' => 'string',
                ],
                'ends_with' => [
                    'expected_type' => 'string',
                ],
                'is_empty' => [
                    'expected_type' => 'boolean',
                    'format' => [
                        'type' => 'enum',
                        'values' => [true]
                    ]
                ],
                'is_not_empty' => [
                    'expected_type' => 'boolean',
                    'format' => [
                        'type' => 'enum',
                        'values' => [true]
                    ]
                ], 
                'content_length_equals' => [
                    'expected_type' => 'number'
                ],
                'content_length_does_not_equal' => [
                    'expected_type' => 'number'
                ],
                'content_length_greater_than' => [
                    'expected_type' => 'number'
                ],
                'content_length_greater_than_or_equal_to' => [
                    'expected_type' => 'number'
                ],
                'content_length_less_than' => [
                    'expected_type' => 'number'
                ],
                'content_length_less_than_or_equal_to' => [
                    'expected_type' => 'number'
                ]
            ]
        ],
        'url' => [
            'comparators' => [
                'equals' => [
                    'expected_type' => 'string',
                ],
                'does_not_equal' => [
                    'expected_type' => 'string',
                ],
                'contains' => [
                    'expected_type' => 'string',
                ],
                'does_not_contain' => [
                    'expected_type' => 'string',
                ],
                'starts_with' => [
                    'expected_type' => 'string',
                ],
                'ends_with' => [
                    'expected_type' => 'string',
                ],
                'is_empty' => [
                    'expected_type' => 'boolean',
                    'format' => [
                        'type' => 'enum',
                        'values' => [true]
                    ]
                ],
                'is_not_empty' => [
                    'expected_type' => 'boolean',
                    'format' => [
                        'type' => 'enum',
                        'values' => [true]
                    ]
                ],
                'content_length_equals' => [
                    'expected_type' => 'number'
                ],
                'content_length_does_not_equal' => [
                    'expected_type' => 'number'
                ],
                'content_length_greater_than' => [
                    'expected_type' => 'number'
                ],
                'content_length_greater_than_or_equal_to' => [
                    'expected_type' => 'number'
                ],
                'content_length_less_than' => [
                    'expected_type' => 'number'
                ],
                'content_length_less_than_or_equal_to' => [
                    'expected_type' => 'number'
                ]
            ]
        ],
        'email' => [
            'comparators' => [
                'equals' => [
                    'expected_type' => 'string',
                ],
                'does_not_equal' => [
                    'expected_type' => 'string',
                ],
                'contains' => [
                    'expected_type' => 'string',
                ],
                'does_not_contain' => [
                    'expected_type' => 'string',
                ],
                'starts_with' => [
                    'expected_type' => 'string',
                ],
                'ends_with' => [
                    'expected_type' => 'string',
                ],
                'is_empty' => [
                    'expected_type' => 'boolean',
                    'format' => [
                        'type' => 'enum',
                        'values' => [true]
                    ]
                ],
                'is_not_empty' => [
                    'expected_type' => 'boolean',
                    'format' => [
                        'type' => 'enum',
                        'values' => [true]
                    ]
                ],
                'content_length_equals' => [
                    'expected_type' => 'number'
                ],
                'content_length_does_not_equal' => [
                    'expected_type' => 'number'
                ],
                'content_length_greater_than' => [
                    'expected_type' => 'number'
                ],
                'content_length_greater_than_or_equal_to' => [
                    'expected_type' => 'number'
                ],
                'content_length_less_than' => [
                    'expected_type' => 'number'
                ],
                'content_length_less_than_or_equal_to' => [
                    'expected_type' => 'number'
                ]
            ]
        ],
        'phone_number' => [
            'comparators' => [
                'equals' => [
                    'expected_type' => 'string',
                ],
                'does_not_equal' => [
                    'expected_type' => 'string',
                ],
                'contains' => [
                    'expected_type' => 'string',
                ],
                'does_not_contain' => [
                    'expected_type' => 'string',
                ],
                'starts_with' => [
                    'expected_type' => 'string',
                ],
                'ends_with' => [
                    'expected_type' => 'string',
                ],
                'is_empty' => [
                    'expected_type' => 'boolean',
                    'format' => [
                        'type' => 'enum',
                        'values' => [true]
                    ]
                ],
                'is_not_empty' => [
                    'expected_type' => 'boolean',
                    'format' => [
                        'type' => 'enum',
                        'values' => [true]
                    ]
                ],
                'content_length_equals' => [
                    'expected_type' => 'number'
                ],
                'content_length_does_not_equal' => [
                    'expected_type' => 'number'
                ],
                'content_length_greater_than' => [
                    'expected_type' => 'number'
                ],
                'content_length_greater_than_or_equal_to' => [
                    'expected_type' => 'number'
                ],
                'content_length_less_than' => [
                    'expected_type' => 'number'
                ],
                'content_length_less_than_or_equal_to' => [
                    'expected_type' => 'number'
                ]
            ]
        ],
        'number' => [
            'comparators' => [
                'equals' => [
                    'expected_type' => 'number',
                ],
                'does_not_equal' => [
                    'expected_type' => 'number',
                ],
                'greater_than' => [
                    'expected_type' => 'number',
                ],
                'less_than' => [
                    'expected_type' => 'number',
                ],
                'greater_than_or_equal_to' => [
                    'expected_type' => 'number',
                ],
                'less_than_or_equal_to' => [
                    'expected_type' => 'number',
                ],
                'is_empty' => [
                    'expected_type' => 'boolean',
                    'format' => [
                        'type' => 'enum',
                        'values' => [true]
                    ]
                ],
                'is_not_empty' => [
                    'expected_type' => 'boolean',
                    'format' => [
                        'type' => 'enum',
                        'values' => [true]
                    ]
                ],
                'content_length_equals' => [
                    'expected_type' => 'number'
                ],
                'content_length_does_not_equal' => [
                    'expected_type' => 'number'
                ],
                'content_length_greater_than' => [
                    'expected_type' => 'number'
                ],
                'content_length_greater_than_or_equal_to' => [
                    'expected_type' => 'number'
                ],
                'content_length_less_than' => [
                    'expected_type' => 'number'
                ],
                'content_length_less_than_or_equal_to' => [
                    'expected_type' => 'number'
                ]
            ]
        ],
        'checkbox' => [
            'comparators' => [
                'equals' => [
                    'expected_type' => 'boolean',
                    'format' => [
                        'type' => 'enum',
                        'values' => [true]
                    ]
                ],
                'does_not_equal' => [
                    'expected_type' => 'boolean',
                    'format' => [
                        'type' => 'enum',
                        'values' => [true]
                    ]
                ],

            ]
        ],
        'select' => [
            'comparators' => [
                'equals' => [
                    'expected_type' => 'string',
                ],
                'does_not_equal' => [
                    'expected_type' => 'string',
                ],
                'is_empty' => [
                    'expected_type' => 'boolean',
                    'format' => [
                        'type' => 'enum',
                        'values' => [true]
                    ]
                ],
                'is_not_empty' => [
                    'expected_type' => 'boolean',
                    'format' => [
                        'type' => 'enum',
                        'values' => [true]
                    ]
                ]
            ]
        ],
        'multi_select' => [
            'comparators' => [
                'contains' => [
                    'expected_type' => ['object', 'string'],
                    'format' => [
                        'type' => 'uuid',
                    ]
                ],
                'does_not_contain' => [
                    'expected_type' => ['object', 'string'],
                    'format' => [
                        'type' => 'uuid',
                    ]
                ],
                'is_empty' => [
                    'expected_type' => 'boolean',
                    'format' => [
                        'type' => 'enum',
                        'values' => [true]
                    ]
                ],
                'is_not_empty' => [
                    'expected_type' => 'boolean',
                    'format' => [
                        'type' => 'enum',
                        'values' => [true]
                    ]
                ]
            ]
        ],
        'date' => [
            'comparators' => [
                'equals' => [
                    'expected_type' => 'string',
                    'format' => [
                        'type' => 'date'
                    ]
                ],
                'before' => [
                    'expected_type' => 'string',
                    'format' => [
                        'type' => 'date'
                    ]
                ],
                'after' => [
                    'expected_type' => 'string',
                    'format' => [
                        'type' => 'date'
                    ]
                ],
                'on_or_before' => [
                    'expected_type' => 'string',
                    'format' => [
                        'type' => 'date'
                    ]
                ],
                'on_or_after' => [
                    'expected_type' => 'string',
                    'format' => [
                        'type' => 'date'
                    ]
                ],
                'is_empty' => [
                    'expected_type' => 'boolean',
                    'format' => [
                        'type' => 'enum',
                        'values' => [true]
                    ]
                ],
                'is_not_empty' => [
                    'expected_type' => 'boolean',
                    'format' => [
                        'type' => 'enum',
                        'values' => [true]
                    ]
                ],
                'past_week' => [
                    'expected_type' => 'object',
                    'format' => [
                        'type' => 'empty',
                        'values' => '{}'
                    ]
                ],
                'past_month' => [
                    'expected_type' => 'object',
                    'format' => [
                        'type' => 'empty',
                        'values' => '{}'
                    ]
                ],
                'past_year' => [
                    'expected_type' => 'object',
                    'format' => [
                        'type' => 'empty',
                        'values' => '{}'
                    ]
                ],
                'next_week' => [
                    'expected_type' => 'object',
                    'format' => [
                        'type' => 'empty',
                        'values' => '{}'
                    ]
                ],
                'next_month' => [
                    'expected_type' => 'object',
                    'format' => [
                        'type' => 'empty',
                        'values' => '{}'
                    ]
                ],
                'next_year' => [
                    'expected_type' => 'object',
                    'format' => [
                        'type' => 'empty',
                        'values' => '{}'
                    ]
                ]
            ]
        ],
        'files' => [
            'comparators' => [
                'is_empty' => [
                    'expected_type' => 'boolean',
                    'format' => [
                        'type' => 'enum',
                        'values' => [true]
                    ]
                ],
                'is_not_empty' => [
                    'expected_type' => 'boolean',
                    'format' => [
                        'type' => 'enum',
                        'values' => [true]
                    ]
                ]
            ]
        ],
    ];

    private $isConditionCorrect = true;
    private $isActionCorrect = true;
    private $conditionErrors = [];
    private $field = [];
    private $data = [];

    private function checkBaseCondition($condition) 
    {

        if (!isset($condition['value'])) {
            $this->isConditionCorrect = false;
            $this->conditionErrors[] = 'missing condition body';
            return;
        }

        if (!isset($condition['value']['property_meta'])) {
            $this->isConditionCorrect = false;
            $this->conditionErrors[] = 'missing condition property';
            return;
        }

        if (!isset($condition['value']['property_meta']['type'])) {
            $this->isConditionCorrect = false;
            $this->conditionErrors[] = 'missing condition property type';
            return;
        }
        
        if (!isset($condition['value']['operator'])) {
            $this->isConditionCorrect = false;
            $this->conditionErrors[] = 'missing condition operator';
            return;
        }

        if (!isset($condition['value']['value'])) {
            $this->isConditionCorrect = false;
            $this->conditionErrors[] = 'missing condition value';
            return;
        }

        $typeField = $condition['value']['property_meta']['type'];
        $operator = $condition['value']['operator'];
        $value = $condition['value']['value'];

        if (!isset(self::CONDITION_MAPPING[$typeField])) {
            $this->isConditionCorrect = false;
            $this->conditionErrors[] = 'configuration not found for condition type';
            return;
        }

        if (!isset(self::CONDITION_MAPPING[$typeField]['comparators'][$operator])) {
            $this->isConditionCorrect = false;
            $this->conditionErrors[] = 'configuration not found for condition operator';
            return;
        }

        // TODO: find what's causing the issue when saving this validation rule :(

        $type = self::CONDITION_MAPPING[$typeField]['comparators'][$operator]['expected_type'];

        if (is_array($type)) {
            $foundCorrectType = false;
            foreach ($type as $subtype) {
                if ($this->valueHasCorrectType($subtype, $value)) {
                    $foundCorrectType = true;
                }
            }
            if (!$foundCorrectType) {
                $this->isConditionCorrect = false;
            }
        } else {
            if (!$this->valueHasCorrectType($type, $value)) {
                $this->isConditionCorrect = false;
                $this->conditionErrors[] = 'wrong type of condition value';
            }
        }
    }

    private function valueHasCorrectType($type, $value)
    {
        if (
            ($type === 'string' && gettype($value) !== 'string') ||
            ($type === 'boolean' && !is_bool($value)) ||
            ($type === 'number' && !is_numeric($value)) ||
            ($type === 'object' && !is_array($value))
        ) {
            return false;
        }
        return true;
    }

    private function checkConditions($conditions) 
    {
        if (array_key_exists('operatorIdentifier', $conditions)) {
            if (($conditions['operatorIdentifier'] !== 'and') && ($conditions['operatorIdentifier'] !== 'or')) {
                $this->conditionErrors[] = 'missing operator';
                $this->isConditionCorrect = false;
                return;
            }

            if (isset($conditions['operatorIdentifier']['children'])) {
                $this->conditionErrors[] = 'extra condition';
                $this->isConditionCorrect = false;
                return;
            }

            if (!is_array($conditions['children'])) {
                $this->conditionErrors[] = 'wrong sub-condition type';
                $this->isConditionCorrect = false;
                return;
            }

            foreach ($conditions['children'] as &$child) {
                $this->checkConditions($child);
            }
        } else if (isset($conditions['identifier'])) {
            $this->checkBaseCondition($conditions);
        }
    }

    private function checkActions($conditions) 
    {
        if (is_array($conditions) && count($conditions) > 0) {
            foreach($conditions as $val){
                if (!in_array($val, static::ACTIONS_VALUES) || 
                    (in_array($this->field["type"], ['nf-text', 'nf-code', 'nf-page-break', 'nf-divider', 'nf-image']) && !in_array($val, ['hide-block'])) ||
                    (isset($this->field["hidden"]) && $this->field["hidden"] && !in_array($val, ['show-block', 'require-answer'])) || 
                    (isset($this->field["required"]) && $this->field["required"] && !in_array($val, ['make-it-optional', 'hide-block', 'disable-block'])) || 
                    (isset($this->field["disabled"]) && $this->field["disabled"] && !in_array($val, ['enable-block', 'require-answer', 'make-it-optional']))
                ) {
                    $this->isActionCorrect = false;
                    break;
                }
            }
        } else {
            $this->isActionCorrect = false;
        }
    }

    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value) 
    {
        $this->setProperty($attribute);
        if(isset($value["conditions"])){
            $this->checkConditions($value["conditions"]);
            $this->checkActions($value['actions'] ?? null);
        }

        return ($this->isConditionCorrect && $this->isActionCorrect);
    }

    /**
     * Get the validation error message.
     *
     */
    public function message() 
    {
        $message = null;
        if (! $this->isConditionCorrect) {
            $message = 'The logic conditions for '.$this->field['name'].' are not complete.';
        } else if (! $this->isActionCorrect) {
            $message = 'The logic actions for '.$this->field['name'].' are not valid.';
        }
        if (count($this->conditionErrors) > 0) {
            return $message . ' Error detail(s): '.implode(', ', $this->conditionErrors);
        }

        return $message;
    }

    /**
     * Set the data under validation.
     *
     * @param  array  $data
     * @return $this
     */
    public function setData($data)
    {
        $this->data = $data;
        $this->isConditionCorrect = true;
        $this->isActionCorrect = true;
        $this->conditionErrors = [];

        return $this;
    }

    private function setProperty(string $attributeKey)
    {
        $attributeKey = Str::of($attributeKey)->replace('.logic', '')->toString();
        $this->field = \Arr::get($this->data, $attributeKey);
    }
}
