<?php

namespace Mtc\Modules\Members\Models;

use App\Casts\OptionalEncrypted;
use Carbon\Carbon;
use Exception;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Mtc\Core\Admin\User;
use Mtc\Plugins\Clinic\Src\Logger;

class MemberAttribute extends Model
{
    protected $table = 'member_attributes';

    protected $fillable = [
        'member_id',
        'name',
        'value',
    ];

    protected $casts = [
        'value' => OptionalEncrypted::class,
    ];

    const TYPE_TEXT = 'text';
    const TYPE_TEXTAREA = 'textarea';
    const TYPE_NUMBER = 'number';
    const TYPE_DATE = 'date';
    const TYPE_DATETIME = 'datetime';
    const TYPE_CHECKBOX = 'checkbox';
    const TYPE_SELECT = 'select';
    const TYPE_RADIO = 'radio';

    public static array $types = [
        self::TYPE_TEXT,
        self::TYPE_TEXTAREA,
        self::TYPE_NUMBER,
        self::TYPE_DATE,
        self::TYPE_DATETIME,
        self::TYPE_CHECKBOX,
        self::TYPE_SELECT,
        self::TYPE_RADIO,
    ];

    const ATTR_TEXT_EXAMPLE = 'text_example';
    const ATTR_TEXTAREA_EXAMPLE = 'textarea_example';
    const ATTR_NUMBER_EXAMPLE = 'number_example';
    const ATTR_DATE_EXAMPLE = 'date_example';
    const ATTR_DATETIME_EXAMPLE = 'datetime_example';
    const ATTR_CHECKBOX_SINGLE_EXAMPLE = 'checkbox_single_example';
    const ATTR_CHECKBOX_MULTI_EXAMPLE = 'checkbox_multi_example';
    const ATTR_SELECT_EXAMPLE = 'select_example';
    const ATTR_RADIO_EXAMPLE = 'radio_example';

    protected static array $fields = [
        self::ATTR_TEXT_EXAMPLE => [
            'type' => self::TYPE_TEXT,
            'name' => 'Text Example',
            'required' => true,
        ],
        self::ATTR_TEXTAREA_EXAMPLE => [
            'type' => self::TYPE_TEXTAREA,
            'name' => 'Textarea Example',
            'required' => true,
        ],
        self::ATTR_NUMBER_EXAMPLE => [
            'type' => self::TYPE_NUMBER,
            'name' => 'Number Example',
            'required' => true,
        ],
        self::ATTR_DATE_EXAMPLE => [
            'type' => self::TYPE_DATE,
            'name' => 'Date Example',
            'required' => true,
        ],
        self::ATTR_DATETIME_EXAMPLE => [
            'type' => self::TYPE_DATETIME,
            'name' => 'Date Time Example',
            'required' => true,
        ],
        self::ATTR_CHECKBOX_SINGLE_EXAMPLE => [
            'type' => self::TYPE_CHECKBOX,
            'name' => 'Checkbox Example (Single)',
            'required' => true,
        ],
        self::ATTR_CHECKBOX_MULTI_EXAMPLE => [
            'type' => self::TYPE_CHECKBOX,
            'name' => 'Checkbox Example (Multi)',
            'required' => true,
            'variations' => [
                'Option 1',
                'Option 2',
                'Option 3',
            ],
        ],
        self::ATTR_SELECT_EXAMPLE => [
            'type' => self::TYPE_SELECT,
            'name' => 'Select Example',
            'required' => true,
            'variations' => [
                'Option 1',
                'Option 2',
                'Option 3',
            ],
        ],
        self::ATTR_RADIO_EXAMPLE => [
            'type' => self::TYPE_RADIO,
            'name' => 'Radio Example',
            'required' => true,
            'variations' => [
                'Option 1',
                'Option 2',
                'Option 3',
            ],
        ],
    ];

    /**
     * @return BelongsTo
     */
    public function member(): BelongsTo
    {
        return $this->belongsTo(Member::class, 'member_id');
    }

    /**
     * Field getter. Uses defined, but can as well be implemented through DB.
     *
     * @return array|array[]
     */
    public static function getFields(): array
    {
        return self::$fields;
    }

    /**
     * Validate submitted fields
     *
     * @param array $input
     * @return array
     */
    public static function validate(array $input): array
    {
        $errors = [];

        foreach (self::getFields() as $key => $attribute) {
            $value = $input[$key] ?? null;
            if ($attribute['required'] && ($value === null)) {
                $errors[$key] = "Field '" . $attribute['name'] . "' is required!";
                continue;
            }

            if (!empty($value) && $attribute['type'] === self::TYPE_DATE) {
                try {
                    Carbon::createFromFormat('d/m/Y', $input[$key]);
                } catch (Exception $e) {
                    $errors[$key] = 'Invalid date format!';
                }
            }

            if (!empty($value) && $attribute['type'] === self::TYPE_DATETIME) {
                try {
                    Carbon::createFromFormat('d/m/Y H:i', $input[$key] . ' ' . $input[$key . '_time']);
                } catch (Exception $e) {
                    $errors[$key] = 'Invalid date/time format!';
                }
            }
        }

        return $errors;
    }

    /**
     * Pack data for storing in DB
     *
     * @param array $input
     * @return array
     */
    public static function packDataForStore(array $input): array
    {
        $data = [];
        foreach (self::getFields() as $key => $attribute) {
            if (!isset($input[$key])) {
                continue;
            }
            if ($attribute['type'] === self::TYPE_DATETIME) {
                $data[$key] = Carbon::createFromFormat('d/m/Y', $input[$key])->format('Y-m-d') . ' ' . $input[$key . '_time'];
                continue;
            }
            if ($attribute['type'] === self::TYPE_DATE) {
                $data[$key] = Carbon::createFromFormat('d/m/Y', $input[$key])->format('Y-m-d');
                continue;
            }
            if (self::jsonEncoded($key)) {
                $data[$key] = json_encode($input[$key]);
                continue;
            }
            $data[$key] = $input[$key];
        }
        return $data;
    }


    public static function unpackDataForForm(Member $member)
    {
        $memberAttributes = $member->attributes()
            ->get()
            ->mapWithKeys(function($attribute) {
                return [$attribute->name => $attribute->value];
            })
            ->toArray();

        $attributes = self::getFields();

        foreach ($memberAttributes as $key => $value) {
            if (empty($attributes[$key])) {
                continue;
            }

            if (empty($value)) {
                continue;
            }

            if (self::jsonEncoded($key)) {
                $memberAttributes[$key] = json_decode($value);
                continue;
            }
            if ($attributes[$key]['type'] === self::TYPE_DATE) {
                $memberAttributes[$key] = Carbon::parse($value)->format('d/m/Y');
                continue;
            }
            if ($attributes[$key]['type'] === self::TYPE_DATETIME) {
                $datetime = Carbon::parse($value);
                $memberAttributes[$key] = $datetime->format('d/m/Y');
                $memberAttributes[$key . '_time'] = $datetime->format('H:i');
            }
        }

        return $memberAttributes;
    }

    /**
     * Logs member attribute changes to event log
     *
     * @param Member $member
     * @param array $data
     * @return void
     */
    public static function logChanges(Member $member, array $data): void
    {
        $memberAttributes = $member->attributes()
            ->get()
            ->mapWithKeys(function($attribute) {
                return [$attribute->name => $attribute->value];
            })
            ->toArray();

        $action = empty($memberAttributes) ?
            Logger::ACTION_CREATED :
            Logger::ACTION_UPDATED;

        $logDetails = [];

        foreach ($data as $key => $value) {
            $oldValue = $memberAttributes[$key] ?? null;
            if ($value == $oldValue) {
                continue;
            }
            $logDetails['old'][$key] = self::jsonEncoded($key) ?
                json_decode($oldValue) :
                $oldValue;
            $logDetails['new'][$key] = self::jsonEncoded($key) ?
                json_decode($value) :
                $value;
        }

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

        $adminUser = User::getAdminUser();
        (new Logger($member, $adminUser, $member->id))
            ->log(Logger::MEMBER_ATTRIBUTES, $action, $logDetails);
    }

    /**
     * Whether the field is json_encoded in DB
     *
     * @param string $key
     * @return bool
     */
    public static function jsonEncoded(string $key): bool
    {
        $attributes = self::getFields();

        return $attributes[$key]['type'] === self::TYPE_CHECKBOX &&
            !empty($attributes[$key]['variations']);
    }
}
