<?php

namespace App\Traits;

use App\Rules\Base64FileValidation;
use App\Rules\CentralIntegrityRule;
use App\Rules\IntegrityRule;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;

trait HasDynamicSchema
{

    /**
     * Get the custom data in the model
     *
     * @return array
     */
    public function getCustomDataAttribute(): array
    {
        $data = $this->attributes['custom_data'] ?? [];

        if (is_string($data)) {
            return json_decode($data, true) ?? [];
        }

        return $data;
    }

    /**
     * Get the schema class associated with the model
     *
     * @return object
     */
    protected function getSchemaClass(): object
    {
        $schemaClass = static::class . 'Schema';
        if (!class_exists($schemaClass)) {
            throw new \RuntimeException("Schema class $schemaClass not found.");
        }

        return (new $schemaClass())::latestVersion();
    }

    protected function getSchemaUnitClass(): object
    {
        $schemaClass = static::class . 'SchemaUnit';
        if (!class_exists($schemaClass)) {
            throw new \RuntimeException("Schema class $schemaClass not found.");
        }

        return (new $schemaClass())::latestVersion();
    }

    /**
     * Get the actual schema associated with the data(the schema column of the schema class)
     *
     * @return array
     */
    protected function getSchema(): array
    {
        return $this->getSchemaClass()->schema;
    }

    protected function getSchemaUnit(): array
    {
        return $this->getSchemaUnitClass()->schema;
    }

    /**
     * Get a generated validation array for the data model based of it's schema
     *
     * @return array
     */
    public function getValidationArray(bool $for_update = false, $pim_request = null): array
    {
        if ($pim_request)
            $schema = $this->getSchemaUnit();
        else
            $schema = $this->getSchema();

        $validation_array = $this->getSystemValidationRules();

        foreach ($schema as $column_name => $column_data) {
            if (isset($validation_array[$column_name]) || ($column_data['type'] == "foreign_key" && isset($validation_array[$column_data['options']['relationship_column']]))) {
                continue;
            }
            $validation_array[$column_name] = [];
            if ($for_update) {
                $validation_array[$column_name][] = 'sometimes';
            }

            if (isset($column_data['options'])) {
                $options = $column_data['options'];

                /** Making column required/nullable */
                if (isset($options['required']) && $options['required']) {
                    if ($column_data['type'] == "foreign_key") {
                        $relationship_column = $column_data['options']['relationship_column'];
                        $validation_array[$relationship_column][] = 'required';
                    } else {
                        $validation_array[$column_name][] = 'required';
                    }
                } else {
                    if ($column_data['type'] == "foreign_key") {
                        $relationship_column = $column_data['options']['relationship_column'];
                        $validation_array[$relationship_column][] = 'nullable';
                    } else {
                        $validation_array[$column_name][] = 'nullable';
                    }
                }

                /** Setting max value if applicable */
                if (isset($options['max'])) {
                    $validation_array[$column_name][] = 'max:' . $options['max'];
                }
            }

            /** Setting type validation */
            switch ($column_data['type']) {
                case 'string':
                case 'text':
                case 'tel':
                    $validation_array[$column_name][] = 'string';
                    break;
                case 'email':
                    $validation_array[$column_name][] = 'email';
                    break;
                case 'integer':
                case 'decimal':
                    $validation_array[$column_name][] = 'numeric';
                    break;
                case 'time':
                    $validation_array[$column_name][] = 'date_format:H:i:s';
                    break;
                case 'date':
                    $validation_array[$column_name][] = 'date_format:Y-m-d';
                    break;
                case 'datetime':
                    $validation_array[$column_name][] = 'date_format:Y-m-d H:i:s';
                    break;
                case 'boolean':
                    $validation_array[$column_name][] = 'boolean';
                    break;
                case 'file':
                    $validation_array[$column_name][] = 'file';
                    break;
                case 'base64file':
                    $validation_array[$column_name][] = 'array';
                    $validation_array[$column_name][] = new Base64FileValidation();
                    break;
                case 'enum':
                    $validation_array[$column_name][] = 'array';
                    $validation_array[$column_name][] = 'min:1';
                    $validation_array[$column_name . '.*'][] = 'string';
                    $validation_array[$column_name . '.*'][] = 'in:' . implode(',', array_keys($column_data['options']['answers']));
                    break;
                case 'enum_single':
                    $validation_array[$column_name][] = 'string';
                    $validation_array[$column_name][] = 'in:' . implode(',', array_keys($column_data['options']['answers']));
                    break;
                case 'foreign_key':
                    $relationship_column = $column_data['options']['relationship_column'];
                    if ($for_update) {
                        $validation_array[$relationship_column][] = 'sometimes';
                    }
                    $validation_array[$relationship_column][] = 'numeric';
                    if ($column_data['options']['central']) {
                        $validation_array[$relationship_column][] = new CentralIntegrityRule('exists', $column_data['options']['referenced_table'], 'id');
                    } else {
                        $validation_array[$relationship_column][] = new IntegrityRule('exists', $column_data['options']['referenced_table'], 'id');
                    }
                    break;
                case 'multi_foreign_key':
                    $validation_array[$column_name][] = 'array';
                    if ($for_update) {
                        $validation_array[$column_name][] = 'sometimes';
                    }
                    $keyValidationRule = $column_data['options']['central'] ?
                        new CentralIntegrityRule('exists', $column_data['options']['referenced_table'], 'id') :
                        new IntegrityRule('exists', $column_data['options']['referenced_table'], 'id');
                    $validation_array[$column_name . '.*'][] = $keyValidationRule;
                    break;
                case 'list':
                    $validation_array[$column_name][] = 'array';
                    break;
            }
        }

        return $validation_array;
    }


    public function getValidationArrayV2(bool $for_update = false, $pim_request = null): array
    {
        if ($pim_request)
            $schema = $this->getSchemaUnit();
        else
            $schema = $this->getSchema();

        $validation_array = [];
        $extra_validation = $this->getSystemValidationRules();

        foreach ($schema as $column_name => $column_data) {
            $validation_array[$column_name] = [];
            if ($for_update) {
                $validation_array[$column_name][] = 'sometimes';
            }

            if (isset($column_data['options'])) {
                $options = $column_data['options'];

                /** Making column required/nullable */
                if (isset($options['required']) && $options['required']) {
                    if ($column_data['type'] == "foreign_key") {
                        $relationship_column = $column_data['options']['relationship_column'];
                        $validation_array[$relationship_column][] = 'required';
                    } else {
                        $validation_array[$column_name][] = 'required';
                    }
                } else {
                    if ($column_data['type'] == "foreign_key") {
                        $relationship_column = $column_data['options']['relationship_column'];
                        $validation_array[$relationship_column][] = 'nullable';
                    } else {
                        $validation_array[$column_name][] = 'nullable';
                    }
                }

                /** Setting max value if applicable */
                if (isset($options['max'])) {
                    $validation_array[$column_name][] = 'max:' . $options['max'];
                }
            }

            /** Setting type validation */
            switch ($column_data['type']) {
                case 'string':
                case 'text':
                case 'tel':
                    $validation_array[$column_name][] = 'string';
                    break;
                case 'email':
                    $validation_array[$column_name][] = 'email';
                    break;
                case 'integer':
                case 'decimal':
                    $validation_array[$column_name][] = 'numeric';
                    break;
                case 'time':
                    $validation_array[$column_name][] = 'date_format:H:i:s';
                    break;
                case 'date':
                    $validation_array[$column_name][] = 'date_format:Y-m-d';
                    break;
                case 'datetime':
                    $validation_array[$column_name][] = 'date_format:Y-m-d H:i:s';
                    break;
                case 'boolean':
                    $validation_array[$column_name][] = 'boolean';
                    break;
                case 'file':
                    $validation_array[$column_name][] = 'file';
                    break;
                case 'base64file':
                    $validation_array[$column_name][] = 'array';
                    $validation_array[$column_name][] = new Base64FileValidation();
                    break;
                case 'enum':
                    $validation_array[$column_name][] = 'array';
                    $validation_array[$column_name][] = 'min:1';
                    $validation_array[$column_name . '.*'][] = 'string';
                    $validation_array[$column_name . '.*'][] = 'in:' . implode(',', array_keys($column_data['options']['answers']));
                    break;
                case 'enum_single':
                    $validation_array[$column_name][] = 'string';
                    $validation_array[$column_name][] = 'in:' . implode(',', array_keys($column_data['options']['answers']));
                    break;
                case 'foreign_key':
                    $relationship_column = $column_data['options']['relationship_column'];
                    if ($for_update) {
                        $validation_array[$relationship_column][] = 'sometimes';
                    }
                    $validation_array[$relationship_column][] = 'numeric';
                    if ($column_data['options']['central']) {
                        $validation_array[$relationship_column][] = new CentralIntegrityRule('exists', $column_data['options']['referenced_table'], 'id');
                    } else {
                        $validation_array[$relationship_column][] = new IntegrityRule('exists', $column_data['options']['referenced_table'], 'id');
                    }
                    break;
                case 'list':
                    $validation_array[$column_name][] = 'array';
                    break;
            }
        }

        return $this->mergeValidationRules($validation_array, $extra_validation);
    }



    protected function mergeValidationRules($base_validation, $custom_validation): array
    {
        $merged_array = [];

        foreach ($base_validation as $key => $value) {
            if (isset($custom_validation[$key])) {
                if (in_array("nullable", $value) || in_array("required", $value)) {
                    $base_val = $value;
                    $custom_val = $custom_validation[$key];
                    $custom_filtered = array_filter($custom_val, function ($value) {
                        return !in_array($value, ["required", "nullable"]);
                    });

                    $merged_array[$key] = array_merge($base_val, $custom_filtered);
                } else {
                    $merged_array[$key] = $value;
                }
            } else {
                $merged_array[$key] = $value;
            }
        }

        return array_merge($custom_validation, $merged_array);
    }

    /**
     * Populate the data property with data
     *
     * @return void
     */
    public function populateCustomData(array $data): void
    {
        $schemaClass = $this->getSchemaClass();
        $schema = $this->getSchema();
        $updated_data = $this->custom_data;

        foreach ($data as $column => $value) {
            if ($schemaClass->hasCustomColumn($column)) {
                switch ($schema[$column]['type']) {
                    case 'file':
                        $updated_data[$column] = saveTenantFile($column);
                        break;
                    case 'multi_foreign_key':
                        $updated_data[$column] = json_encode($value);
                        break;
                    default:
                        $updated_data[$column] = $value;
                        break;
                }
            } elseif ($schemaClass->hasCustomForeignColumn($column)) {
                $updated_data[$column] = (int) $value;
            }
        }

        $this->setAttribute('custom_data', $updated_data);
        $this->saveCustomData();
    }

    /**
     * Get the actual value for an enum column based on the answer key
     *
     * @param string $column The column name
     * @param mixed $value The selected answer key
     * @return mixed The actual value from the enum options
     */
    protected function getEnumColumnValue(string $column, $value)
    {
        $schema = $this->getSchema();

        if (isset($schema[$column]) && isset($schema[$column]['options']['answers'][$value])) {
            return $schema[$column]['options']['answers'][$value];
        }

        return $value;
    }

    /**
     * Get the enum key based on the actual value for an enum column
     *
     * @param string $column The column name
     * @param mixed $value The actual value from the enum options
     * @return mixed|null The corresponding key for the value, or null if not found
     */
    protected function getEnumColumnKey(string $column, $value)
    {
        $schema = $this->getSchema();

        if (isset($schema[$column]) && isset($schema[$column]['options']['answers'])) {
            $answers = array_flip($schema[$column]['options']['answers']);
            return $answers[$value] ?? null;
        }

        return null;
    }

    /**
     * Map keys with their values based on a specific column
     *
     * @param string $column The column name
     * @param array $keys The array of keys
     * @return array The array of key-value pairs
     */
    protected function mapKeysWithValues(string $column, array $keys)
    {
        $schema = $this->getSchema();
        $mapped = [];

        if (isset($schema[$column]) && isset($schema[$column]['options']['answers'])) {
            $column_answers = $schema[$column]['options']['answers'];

            foreach ($keys as $key) {
                if (array_key_exists($key, $column_answers)) {
                    $mapped[$key] = $column_answers[$key];
                }
            }
        }

        return $mapped;
    }



    /**
     * Save data property into database
     *
     * @return void
     */
    public function saveCustomData(): void
    {
        $this->custom_data = json_encode($this->custom_data);
        $this->save();
    }

    /**
     * Get the data that is not in the schema but should be appended when retrieving model
     *
     * @return array
     */
    abstract public function appends(): array;

    /**
     * Get the attributes that should be hidden when the model is getting retrieved
     *
     * @return array
     */
    abstract public function hidden(): array;

    /**
     * Get the system validations set for the model
     *
     * @return array
     */
    abstract public function getSystemValidationRules(): array;

    /**
     * Insert data into the system default columns
     *
     * @param array $data
     * @return void
     */
    abstract public function insertSystemData(array $data): void;

    /**
     * update system default columns
     *
     * @param array $data
     * @return void
     */
    abstract public function updateSystemData(array $data): void;

    /**
     * Attempt to delete the data
     *
     * @return Response
     */
    abstract public function callDeleteMethod(): JsonResponse | Response;
}

