<?php

use App\Http\Controllers\CategoryController;
use App\Models\Category;
use function Pest\Laravel\delete;
use function Pest\Laravel\get;
use function Pest\Laravel\patch;

covers(CategoryController::class);

dataset('invalid-values', [null, true, 50]);
dataset('valid-values', ['hello', 'hello there', 'h#llo', 'h3llo']);

describe('categories.index', function () {
    test('can be rendered to guests', function () {
        get(route('categories.index'))->assertOk();
    });

    test('can be rendered to logged in users', function () {
        asAdmin()->get(route('categories.index'))->assertOk();
    });

    test('contains categories', function () {
        $response = get(route('categories.index'));

        expect($response['categories'])
            ->toBeEloquentCollection()
            ->toContainOnlyInstancesOf(Category::class)
            ->toEqual(Category::all());
    });

    test('contains trashedCategories', function () {
        $response = get(route('categories.index'));

        expect($response['trashedCategories'])
            ->toBeEloquentCollection()
            ->toContainOnlyInstancesOf(Category::class)
            ->toEqual(Category::onlyTrashed()->get());
    });
});

describe('categories.create', function () {
    test("can't be rendered to guests", function () {
        get(route('categories.create'))->assertForbidden();
    });

    test('can be rendered to logged in users', function () {
        asAdmin()->get(route('categories.create'))->assertOk();
    });
});

describe('categories.store', function () {
    test("can't be accessed by guests", function () {
        $category = Category::factory()->make();

        $this->post(
            route('categories.store'),
            ['name' => $category->name]
        )->assertForbidden();
        expect($category)->not->toMatchDatabaseRecord();
    });

    test('can be accessed by logged in users', function () {
        $category = Category::factory()->make();

        asAdmin()->post(
            route('categories.store'),
            ['name' => $category->name]
        )->assertRedirect(route('categories.index'));
        expect($category)->toMatchDatabaseRecord();
    });

    test('stores valid categories', function (string $name) {
        $category = Category::factory()->make(['name' => $name]);

        asAdmin()->post(
            route('categories.store'),
            ['name' => $name]
        )->assertRedirect(route('categories.index'));
        expect($category)->toMatchDatabaseRecord();
    })->with('valid-values');

    test('does not store invalid categories', function (mixed $name) {
        $category = Category::factory()->make(['name' => $name]);

        asAdmin()->post(
            route('categories.store'),
            ['name' => $name],
        )->assertRedirect();
        expect($category)->not->toMatchDatabaseRecord();
    })->with('invalid-values');
});

describe('categories.show', function () {
    test('can be rendered to guests', function () {
        get(route('categories.show', Category::factory()->create()))
            ->assertOk();
    });

    test('can be rendered to logged in users', function () {
        asAdmin()
            ->get(route('categories.show', Category::factory()->create()))
            ->assertOk();
    });

    test('contains the category', function () {
        $category = Category::factory()->create();

        $response = get(route('categories.show', $category));

        expect($response['category'])
            ->id->toBe($category->id)
            ->name->toBe($category->name);
    });

    test('can show trashed categories', function () {
        $category = Category::factory()->trashed()->create();

        $response = get(route('categories.show', $category));

//      @formatter:off
        expect($response['category'])
            ->id->toBe($category->id)
            ->name->toBe($category->name)
            ->and($category)->toBeSoftDeleted();
//      @formatter:on
    });
});

describe('categories.edit', function () {
    test("can't be rendered to guests", function () {
        $category = Category::factory()->create();

        get(route('categories.edit', $category))
            ->assertForbidden();
    });

    test('can be rendered to logged in users', function () {
        $category = Category::factory()->create();

        asAdmin()
            ->get(route('categories.edit', $category))
            ->assertOk();
    });

    test('contains the category', function () {
        $category = Category::factory()->create();

        $response = asAdmin()->get(route('categories.edit', $category));

        expect($response['category'])
            ->id->toBe($category->id)
            ->name->toBe($category->name);
    });
});

describe('categories.update', function () {
    test("can't be accessed by guests", function () {
        $category = Category::factory()->create();
        $updated = Category::factory()->make();

        patch(
            route('categories.update', $category),
            ['name' => $updated->name]
        )->assertForbidden();
        expect($category)->toBeInDatabaseExactly();
    });

    test('can be accessed by logged in users', function () {
        $category = Category::factory()->create();
        $updated = Category::factory()->make();

        asAdmin()->patch(
            route('categories.update', $category),
            ['name' => $updated->name]
        )->assertRedirect(route('categories.index', absolute: false));

        $category->refresh();

        expect($category)->toMatchObject($updated);
    });

    test('updates categories with valid values', function (string $name) {
        $category = Category::factory()->create();

        asAdmin()->patch(
            route('categories.update', $category),
            ['name' => $name]
        )->assertRedirect(route('categories.index', absolute: false));

        $category->refresh();

        expect($category->name)->toEqual($name);
    })->with('valid-values');

    test(
        'does not update categories with invalid values',
        function (mixed $name) {
            $category = Category::factory()->create();

            asAdmin()->patch(
                route('categories.update', $category),
                ['name' => $name]
            )->assertRedirect();

            $updated = Category::find($category->id);
            expect($updated->name)->toEqual($category->name);
        }
    )->with('invalid-values');

    test('restores trashed categories', function () {
        $category = Category::factory()->trashed()->create();

        asAdmin()
            ->patch(route('categories.update', $category), [
                'restore' => 1
            ])->assertRedirect();

        $category->refresh();
        expect($category)->not->toBeSoftDeleted();
    });
});

describe('categories.destroy', function () {
    test("can't be accessed by guests", function () {
        $category = Category::factory()->create();

        delete(route('categories.destroy', $category))
            ->assertForbidden();
        expect($category)->not->toBeSoftDeleted();
    });

    test('can be accessed by logged in users', function () {
        $category = Category::factory()->create();

        asAdmin()
            ->delete(route('categories.destroy', $category))
            ->assertRedirect(route('categories.index', absolute: false));
        expect($category)->toBeSoftDeleted();
    });
});
