<?php

namespace Tests\Unit\Models;

use App\Jobs\ProjectDownloadTiffJob;
use App\Jobs\ProjectMosiacGeneratorJob;
use App\Jobs\ProjectReportGeneratorJob;
use App\Models\Project;

use Illuminate\Bus\PendingBatch;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Bus;
use Mockery;
use Tests\TestCase;

class ProjectTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp(); // TODO: Change the autogenerated stub
    }

    /** @test */
    public function it_should_return_the_correct_attachment_path()
    {
        $project = Project::factory()->create(['name' => 'Test Project']);
        $this->assertStringEndsWith(
            '/storage/test_project/attachments',
            $project->attachment_path
        );
    }

    /** @test */
    public function it_can_add_a_mailing_and_attachment()
    {
        $project = Project::factory()->create();
        $project->mailings()->create([
            'subject' => 'Test Subject',
            'message' => 'Test Message',
        ]);
        $fileFake = \Illuminate\Http\UploadedFile::fake()->create('test.pdf');
        $project->mailings()->first()->addAttachment('test.pdf', $fileFake);

        $this->assertCount(1, $project->mailings);
        $this->assertCount(1, $project->mailings()->first()->attachments);
    }


    /** @test */
    public function when_running_the_seeder_their_are_three_projects(): void
    {
        $this->seed();
        $this->assertCount(3, Project::all());
    }

    /** @test */
    public function when_running_the_seeder_their_are_three_projects_with_the_correct_names(): void
    {
        $this->seed();
        $this->assertEquals('Chemba', Project::find(1)->name);
        $this->assertEquals('Xinavane', Project::find(2)->name);
        $this->assertEquals('Kakira', Project::find(3)->name);
    }

    /** @test */
    public function when_running_the_seeder_their_are_three_projects_with_the_correct_download_paths(): void
    {
        $this->seed();
        $this->assertEquals('chemba', Project::find(1)->download_path);
        $this->assertEquals('xinavane', Project::find(2)->download_path);
        $this->assertEquals('kakira', Project::find(3)->download_path);
    }

    /** @test */
    public function when_not_all_mosaics_are_present_it_should_return_an_exception()
    {
        $this->seed();
        $this->expectException(\Exception::class);
        $this->expectExceptionMessage('Missing mosaics: week_52_2021.tif, week_51_2021.tif, week_50_2021.tif, week_49_2021.tif');
        $project = Project::find(1);
        $lastDate = Carbon::parse('2022-01-01');
        $project->allMosaicsPresent($lastDate);
    }

    /** @test */
    public function when_all_mosaics_are_present_it_should_return_true()
    {
        $project = Mockery::mock(Project::class)->makePartial();
        $project->mail_day = 'Friday';
        $lastDate = Carbon::parse('2022-01-01');
        $project->shouldReceive('getMosaicList')->andReturn(
            collect([
                "chemba/weekly_mosaic/week_52_2021.tif",
                "chemba/weekly_mosaic/week_51_2021.tif",
                "chemba/weekly_mosaic/week_50_2021.tif",
                "chemba/weekly_mosaic/week_49_2021.tif",
            ]));

        $this->assertTrue($project->allMosaicsPresent($lastDate));
    }

    /** @test */
    public function when_not_mosaics_are_present_it_should_throw_an_exception_listing_the_missing_mosiacs()
    {
        $this->expectException(\Exception::class);
        $this->expectExceptionMessage('Missing mosaics: week_49_2020.tif');
        $project = Mockery::mock(Project::class)->makePartial();
        $lastDate = Carbon::parse('2021-01-01');
        $project->shouldReceive('getMosaicList')->andReturn(
            collect([
                "chemba/weekly_mosaic/week_52_2020.tif",
                "chemba/weekly_mosaic/week_51_2020.tif",
                "chemba/weekly_mosaic/week_50_2020.tif",
            ]));

        $project->allMosaicsPresent($lastDate);
    }

    /** @test */
    public function when_all_mosaics_are_present_is_called_with_future_date_it_should_throw_an_expection()
    {
        $this->expectException(\Exception::class);
        $this->expectExceptionMessage('Mosaic can\'t be generated for the future. Change the end date.');
        $this->seed();
        $project = Project::find(1);
        Carbon::setTestNow(Carbon::parse('2020-01-01'));
        $lastDate = Carbon::parse('2021-01-01');
        // stub getMosiacList() to return a list containing four filenames;
        ($project->allMosaicsPresent($lastDate));
    }

    /** @test */
    public function getMosiacFileListByEndDate_should_return_four_filenames()
    {
        $this->seed();
        /* @var Project $project */
        $project = Project::find(1);
        $lastDate = Carbon::parse('2021-01-01');
        $list = iterator_to_array($project::getMosaicFilenameListByEndDate($lastDate));
        $this->assertCount(4, $list);
        $this->assertEquals([
            'week_52_2020.tif',
            'week_51_2020.tif',
            'week_50_2020.tif',
            'week_49_2020.tif',
        ], $list);
    }

    /** @test */
    public function when_getFileDownloadsFor_is_called_it_returns_a_collection_of_seven_downloads_jobs()
    {
        /* @var Project $project */
        $project = Project::create([
            'name'          => 'project_name',
            'download_path' => 'project_download_path',
        ]);

        $downloads = $project->getFileDownloadsFor(new Carbon('2024-06-18'), 7);
        $this->assertCount(28, $downloads);
        collect($downloads)->each(fn($job) => $this->assertInstanceOf(ProjectDownloadTiffJob::class, $job));
    }

    /** @test */
    public function when_getMosaicsFor_is_called_it_returns_a_collection_of_4_jobs()
    {
        /* @var Project $project */
        $project = Project::create([
            'name'          => 'project_name',
            'download_path' => 'project_download_path',
        ]);

        $mosaics = $project->getMosaicsFor(new Carbon('2023-01-01'));
        $this->assertCount(4, $mosaics);
        collect($mosaics)->each(fn($job) => $this->assertInstanceOf(ProjectMosiacGeneratorJob::class, $job));
    }

    /** @test */
    public function when_getMosaicsFor_is_called_it_returns_a_collection_of_4_jobs_with_correct_week_numbers()
    {
        /* @var Project $project */
        $project = Project::create([
            'name'          => 'project_name',
            'download_path' => 'project_download_path',
            'mail_day'      => 'Friday',
        ]);

        $mosaics = $project->getMosaicsFor(new Carbon('2024-07-02'));
        $this->assertCount(4, $mosaics);
        collect($mosaics)->each(fn($job) => $this->assertInstanceOf(ProjectMosiacGeneratorJob::class, $job));

        $this->assertEquals([
            "Week_27_2024",
            "Week_26_2024",
            "Week_25_2024",
            "Week_24_2024",
        ],
            collect($mosaics)->map(function ($job) {
                return $job->mosaic->name;
            })->toArray());
    }
    /** @test */
    public function when_getMosaicsFor_is_called_it_returns_a_collection_of_4_jobs_with_correct_week_numbers_when_end_date_is_mail_day()
    {
        /* @var Project $project */
        $project = Project::create([
            'name'          => 'project_name',
            'download_path' => 'project_download_path',
            'mail_day'      => 'Friday',
        ]);

        $mosaics = $project->getMosaicsFor(new Carbon('2024-07-05'));
        $this->assertCount(4, $mosaics);
        collect($mosaics)->each(fn($job) => $this->assertInstanceOf(ProjectMosiacGeneratorJob::class, $job));

        $this->assertEquals([
            "Week_28_2024",
            "Week_27_2024",
            "Week_26_2024",
            "Week_25_2024",
        ],
            collect($mosaics)->map(function ($job) {
                return $job->mosaic->name;
            })->toArray());
    }

    /** @test */
    public function when_getMosaicsFor_is_called_it_returns_a_collection_of_4_jobs_with_where_current_day_is_in_week_26_but_the_mail_day_is_in_week_27(
    )
    {
        /* @var Project $project */
        $project = Project::create([
            'name'          => 'project_name',
            'download_path' => 'project_download_path',
            'mail_day'      => 'Friday',
        ]);

        $mosaics = $project->getMosaicsFor(new Carbon('2024-06-28'));
        $this->assertCount(4, $mosaics);
        collect($mosaics)->each(fn($job) => $this->assertInstanceOf(ProjectMosiacGeneratorJob::class, $job));

        $this->assertEquals([
            "Week_27_2024",
            "Week_26_2024",
            "Week_25_2024",
            "Week_24_2024",
        ],
            collect($mosaics)->map(function ($job) {
                return $job->mosaic->name;
            })->toArray());
    }

    /** @test */
    public function when_getReport_is_called_it_returns_a_jobs()
    {
        /* @var Project $project */
        $project = Project::create([
            'name'          => 'project_name',
            'download_path' => 'project_download_path',
        ]);

        $job = $project->getReportFor(new Carbon('2023-01-01'));

        $this->assertInstanceOf(ProjectReportGeneratorJob::class, $job);
    }

    /** @test */
    public function it_can_create_a_chain_of_batches_that_result_in_a_report()
    {
        /* @var Project $project */
        $project = Project::create([
            'name'          => 'project_name',
            'download_path' => 'project_download_path',
        ]);

        Bus::fake();
        $project->scheduleReport(new Carbon('2023-01-01'), 7);
        Bus::assertChained([
            Bus::chainedBatch(function (PendingBatch $batch) {
                $batch;
                return $batch->jobs->count() === 28;
            }),
            Bus::chainedBatch(function (PendingBatch $batch) {
                return $batch->jobs->count() === 4;
            }),
            Bus::chainedBatch(function (PendingBatch $batch) {
                return $batch->jobs->count() === 1;
            }),
        ]);
    }

    /**
     * @test
     * @dataProvider scheduleDayProvider
     */
    public function when_friday_and_first_week_it_should_schedule($date, $day, $result)
    {


        Carbon::setTestNow(Carbon::parse($day));
        $project = Project::factory()->create([
            'mail_frequency' => 'weekly',
            'mail_day'       => 'Friday',
        ]);

        $this->assertEquals($result, $project->shouldSchedule());
    }

    public static function scheduleDayProvider()
    {
        return [
            ['date' => '2024-03-01', 'day' => 'Friday', 'result' => true],
            ['date' => '2024-03-02', 'day' => 'Saturday', 'result' => false],
            ['date' => '2024-03-03', 'day' => 'Sunday', 'result' => false],
            ['date' => '2024-03-04', 'day' => 'Monday', 'result' => false],
            ['date' => '2024-03-05', 'day' => 'Tuesday', 'result' => false],
            ['date' => '2024-03-06', 'day' => 'Wednesday', 'result' => false],
            ['date' => '2024-03-07', 'day' => 'Thursday', 'result' => false],

            ['date' => '2024-03-08', 'day' => 'Friday', 'result' => true],
            ['date' => '2024-03-09', 'day' => 'Saturday', 'result' => false],
            ['date' => '2024-03-10', 'day' => 'Sunday', 'result' => false],
            ['date' => '2024-03-11', 'day' => 'Monday', 'result' => false],
            ['date' => '2024-03-12', 'day' => 'Tuesday', 'result' => false],
            ['date' => '2024-03-13', 'day' => 'Wednesday', 'result' => false],
            ['date' => '2024-03-14', 'day' => 'Thursday', 'result' => false],
        ];

    }

}
