<?php

namespace Tests\Unit;

use App\Traits\AppliesModelConditions;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Mtc\MercuryDataModels\VehicleMake;
use Tests\TestCase;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleAttributeValue;

class VehicleSalesChannelRulesModelConditionsTest extends TestCase
{
    use AppliesModelConditions;

    protected Vehicle $vehicle;
    protected \Illuminate\Support\Collection $operators;
    protected array $testableFields;
    #[\Override]
    protected function setUp(): void
    {
        parent::setUp();

        $this->operators = collect(config('reports.operators', []));

        $this->vehicle = new Vehicle();
        $this->vehicle->is_published = true;
        $this->vehicle->auto_trader_id = 123456789;
        $this->vehicle->price = 9999.99;
        $this->vehicle->registration_number = 'TEST_NUMBER';
        $this->vehicle->created_at = Carbon::now()->subDays(5);
        $this->vehicle->created_at_since = 5;
        $this->vehicle->setRelation('mediaUses', collect([new \stdClass(), new \stdClass()]));
        $this->vehicle->make()->associate(new VehicleMake(['name' => 'Toyota']));

        $this->testableFields = [
            'registration_number',
            'auto_trader_id',
            'make.name',
            'mediaUses.count',
            'price',
            'created_at_since',
        ];
    }
    public function testAllOperatorsFalse(): void
    {
        foreach ($this->testableFields as $field) {
            $fieldValue = $this->getFieldValue($this->vehicle, $field);

            foreach ($this->operators as $operator) {
                $condition = [
                    'value' => [
                        'column' => $field,
                        'operator' => $operator,
                        'value' => $this->getFalseTestValue($operator, $fieldValue),
                    ],
                ];

                $this->assertFalse(
                    $this->applyModelConditions($this->vehicle, [$condition]),
                    json_encode([$condition, $fieldValue])
                );
            }
        }
    }

    public function testAllOperatorsTrue(): void
    {
        foreach ($this->testableFields as $field) {
            $fieldValue = $this->getFieldValue($this->vehicle, $field);

            foreach ($this->operators as $operator) {
                $condition = [
                    'value' => [
                        'column' => $field,
                        'operator' => $operator,
                        'value' => $this->getTrueTestValue($operator, $fieldValue),
                    ],
                ];

                $this->assertTrue(
                    $this->applyModelConditions($this->vehicle, [$condition]),
                    json_encode([$condition, $fieldValue])
                );
            }
        }
    }

    public function testBooleanFieldIsTrue(): void
    {
        $field = 'is_published';
        $conditionEqual = [
            'value' => [
                'column' => $field,
                'operator' => '=',
                'value' => true,
            ],
        ];

        $conditionNotEqual = [
            'value' => [
                'column' => $field,
                'operator' => '!=',
                'value' => false,
            ],
        ];

        $this->assertTrue($this->applyModelConditions($this->vehicle, [$conditionEqual]));
        $this->assertTrue($this->applyModelConditions($this->vehicle, [$conditionNotEqual]));
    }

    public function testBooleanFieldIsFalse(): void
    {
        $field = 'is_published';
        $conditionEqual = [
            'value' => [
                'column' => $field,
                'operator' => '=',
                'value' => false,
            ],
        ];

        $conditionNotEqual = [
            'value' => [
                'column' => $field,
                'operator' => '!=',
                'value' => true,
            ],
        ];

        $this->assertFalse($this->applyModelConditions($this->vehicle, [$conditionEqual]));
        $this->assertFalse($this->applyModelConditions($this->vehicle, [$conditionNotEqual]));
    }

    public function testBooleanAttributeConditionMatches(): void
    {
        $this->vehicle->setRelation('attributeValues', collect([
            new VehicleAttributeValue([
                'slug' => 'pull-to-at-connect',
                'type' => 'boolean',
                'value_integer' => 1,
            ]),
        ]));

        $condition = [
            'value' => [
                'column' => 'attribute.pull-to-at-connect',
                'operator' => '=',
                'value' => 'yes',
            ],
        ];

        $this->assertTrue($this->applyModelConditions($this->vehicle, [$condition]));
    }

    public function testBooleanAttributeConditionDoesNotMatch(): void
    {
        $this->vehicle->setRelation('attributeValues', collect([
            new VehicleAttributeValue([
                'slug' => 'pull-to-at-connect',
                'type' => 'boolean',
                'value_integer' => 0,
            ]),
        ]));

        $condition = [
            'value' => [
                'column' => 'attribute.pull-to-at-connect',
                'operator' => '=',
                'value' => 'yes',
            ],
        ];

        $this->assertFalse($this->applyModelConditions($this->vehicle, [$condition]));
    }

    public function testTextAttributeCondition(): void
    {
        $this->vehicle->setRelation('attributeValues', collect([
            new VehicleAttributeValue([
                'slug' => 'connect-status',
                'type' => 'text',
                'value_text' => 'approved',
            ]),
        ]));

        $matching = [
            'value' => [
                'column' => 'attribute.connect-status',
                'operator' => '=',
                'value' => 'approved',
            ],
        ];
        $nonMatching = [
            'value' => [
                'column' => 'attribute.connect-status',
                'operator' => '=',
                'value' => 'rejected',
            ],
        ];

        $this->assertTrue($this->applyModelConditions($this->vehicle, [$matching]));
        $this->assertFalse($this->applyModelConditions($this->vehicle, [$nonMatching]));
    }

    public function testMissingAttributeConditionDoesNotMatch(): void
    {
        $this->vehicle->setRelation('attributeValues', collect());

        $condition = [
            'value' => [
                'column' => 'attribute.pull-to-at-connect',
                'operator' => '=',
                'value' => 'yes',
            ],
        ];

        $this->assertFalse($this->applyModelConditions($this->vehicle, [$condition]));
    }

    private function getFalseTestValue(string $operator, $currentValue)
    {
        return match ($operator) {
            '!=' => $currentValue,
            'in' => ['some-other-id'],
            'like', 'like%', '%like%' => 'something-else',
            '>', '>=' => (float)$currentValue + 1,
            '<', '<=' => (float)$currentValue - 1,
            default => 'some-other-value',
        };
    }


    private function getTrueTestValue(string $operator, $currentValue)
    {
        return match ($operator) {
            '!=' => 'some-other-value',
            'in' => [$currentValue],
            'like' => (string)$currentValue,
            'like%' => !is_int($currentValue) ? substr((string) $currentValue, 0, -1) : $currentValue,
            '%like%' => !is_int($currentValue) ? substr((string) $currentValue, 1, -1) : $currentValue,
            '>', '>=' => (float)$currentValue - 1,
            '<', '<=' => (float)$currentValue + 1,
            default => $currentValue,
        };
    }

    private function getFieldValue($vehicle, $field)
    {
        $attributes = $vehicle->getAttributes();
        if (str_contains((string) $field, '.')) {
            $fieldParts = explode('.', (string) $field);
            $relation = $vehicle->{$fieldParts[0]};

            if ($relation) {
                if (isset($relation->{$fieldParts[1]})) {
                    return $relation->{$fieldParts[1]};
                } elseif ($fieldParts[1] === 'count') {
                    return $relation->count();
                } else {
                    return null;
                }
            } else {
                return null;
            }
        } else {
            return Arr::get($attributes, $field, null);
        }
    }
}
