<?php

namespace Tests\Unit\Support;

use App\Support\DateRange;
use Illuminate\Support\Carbon;
use InvalidArgumentException;
use Tests\TestCase;

class DateRangeTest extends TestCase
{
    public function test_it_exposes_from_and_to(): void
    {
        $from = Carbon::parse('2026-07-01 00:00:00');
        $to = Carbon::parse('2026-07-31 23:59:59');

        $range = new DateRange($from, $to);

        $this->assertTrue($from->equalTo($range->from));
        $this->assertTrue($to->equalTo($range->to));
    }

    public function test_it_rejects_a_to_earlier_than_from(): void
    {
        $this->expectException(InvalidArgumentException::class);

        new DateRange(
            Carbon::parse('2026-07-31 00:00:00'),
            Carbon::parse('2026-07-01 00:00:00'),
        );
    }

    public function test_it_allows_a_range_covering_a_single_instant(): void
    {
        $instant = Carbon::parse('2026-07-01 12:00:00');

        $range = new DateRange($instant, $instant);

        $this->assertTrue($range->from->equalTo($range->to));
    }

    public function test_it_builds_today_as_the_current_day(): void
    {
        Carbon::setTestNow('2026-07-15 13:45:00');

        $range = DateRange::preset('today');

        $this->assertSame('2026-07-15 00:00:00', $range->from->toDateTimeString());
        $this->assertSame('2026-07-15 23:59:59', $range->to->toDateTimeString());
    }

    public function test_it_builds_week_month_and_year_presets(): void
    {
        Carbon::setTestNow('2026-07-15 13:45:00');

        $week = DateRange::preset('week');
        $this->assertTrue($week->from->equalTo(Carbon::parse('2026-07-15')->startOfWeek()));
        $this->assertTrue($week->to->equalTo(Carbon::parse('2026-07-15')->endOfWeek()));

        $month = DateRange::preset('month');
        $this->assertSame('2026-07-01 00:00:00', $month->from->toDateTimeString());
        $this->assertSame('2026-07-31 23:59:59', $month->to->toDateTimeString());

        $year = DateRange::preset('year');
        $this->assertSame('2026-01-01 00:00:00', $year->from->toDateTimeString());
        $this->assertSame('2026-12-31 23:59:59', $year->to->toDateTimeString());
    }

    public function test_it_builds_all_as_a_bounded_range_ending_now(): void
    {
        Carbon::setTestNow('2026-07-15 13:45:00');

        $range = DateRange::preset('all');

        $this->assertSame('2026-07-15 23:59:59', $range->to->toDateTimeString());
        $this->assertTrue($range->from->lessThan(Carbon::parse('2000-01-01')));
    }

    public function test_it_rejects_an_unknown_preset(): void
    {
        $this->expectException(InvalidArgumentException::class);

        DateRange::preset('fortnight');
    }

    public function test_it_lists_the_available_presets(): void
    {
        $presets = DateRange::presets();

        $this->assertSame(
            ['today', 'week', 'month', 'year', 'all'],
            array_keys($presets),
        );
        $this->assertSame('Today', $presets['today']);
        $this->assertSame('All Time', $presets['all']);
    }

    public function test_it_lists_each_day_in_the_range(): void
    {
        $range = new DateRange(
            Carbon::parse('2026-07-01 09:30:00'),
            Carbon::parse('2026-07-04 18:00:00'),
        );

        $this->assertSame(
            ['2026-07-01', '2026-07-02', '2026-07-03', '2026-07-04'],
            $range->days(),
        );
    }

    public function test_it_lists_a_single_day_for_a_same_day_range(): void
    {
        $range = new DateRange(
            Carbon::parse('2026-07-01 00:00:00'),
            Carbon::parse('2026-07-01 23:59:59'),
        );

        $this->assertSame(['2026-07-01'], $range->days());
    }

    public function test_it_lists_days_across_a_month_boundary(): void
    {
        $range = new DateRange(
            Carbon::parse('2026-01-30 00:00:00'),
            Carbon::parse('2026-02-02 23:59:59'),
        );

        $this->assertSame(
            ['2026-01-30', '2026-01-31', '2026-02-01', '2026-02-02'],
            $range->days(),
        );
    }

    public function test_it_lists_days_across_a_year_boundary(): void
    {
        $range = new DateRange(
            Carbon::parse('2025-12-30 00:00:00'),
            Carbon::parse('2026-01-02 23:59:59'),
        );

        $this->assertSame(
            ['2025-12-30', '2025-12-31', '2026-01-01', '2026-01-02'],
            $range->days(),
        );
    }

    public function test_it_lists_every_day_of_a_leap_february(): void
    {
        $range = new DateRange(
            Carbon::parse('2028-02-27 00:00:00'),
            Carbon::parse('2028-03-01 23:59:59'),
        );

        $this->assertSame(
            ['2028-02-27', '2028-02-28', '2028-02-29', '2028-03-01'],
            $range->days(),
        );
    }

    public function test_it_lists_days_up_to_the_maximum_span(): void
    {
        $from = Carbon::parse('2026-01-01 00:00:00');
        $range = new DateRange($from, $from->copy()->addDays(DateRange::MAX_DAYS - 1)->endOfDay());

        $this->assertCount(DateRange::MAX_DAYS, $range->days());
    }

    public function test_it_refuses_to_list_days_for_a_range_wider_than_the_maximum(): void
    {
        $from = Carbon::parse('2026-01-01 00:00:00');
        $range = new DateRange($from, $from->copy()->addDays(DateRange::MAX_DAYS)->endOfDay());

        $this->expectException(InvalidArgumentException::class);

        $range->days();
    }

    public function test_it_refuses_to_list_days_for_the_all_preset(): void
    {
        Carbon::setTestNow('2026-07-15 13:45:00');

        $this->expectException(InvalidArgumentException::class);

        DateRange::preset('all')->days();
    }

    public function test_it_is_bucketable_by_day_for_a_normal_range(): void
    {
        $this->assertTrue(DateRange::preset('week')->isBucketableByDay());
        $this->assertTrue(DateRange::preset('month')->isBucketableByDay());
    }

    public function test_it_is_bucketable_by_day_at_the_maximum_span(): void
    {
        $from = Carbon::parse('2026-01-01 00:00:00');
        $range = new DateRange($from, $from->copy()->addDays(DateRange::MAX_DAYS - 1)->endOfDay());

        $this->assertTrue($range->isBucketableByDay());
    }

    public function test_it_is_not_bucketable_by_day_one_day_beyond_the_maximum_span(): void
    {
        $from = Carbon::parse('2026-01-01 00:00:00');
        $range = new DateRange($from, $from->copy()->addDays(DateRange::MAX_DAYS)->endOfDay());

        $this->assertFalse($range->isBucketableByDay());
    }

    public function test_it_is_not_bucketable_by_day_for_the_all_preset(): void
    {
        Carbon::setTestNow('2026-07-15 13:45:00');

        $this->assertFalse(DateRange::preset('all')->isBucketableByDay());
    }

    public function test_is_bucketable_by_day_agrees_with_days_at_every_boundary(): void
    {
        $from = Carbon::parse('2026-01-01 00:00:00');

        $withinBounds = new DateRange($from, $from->copy()->addDays(DateRange::MAX_DAYS - 1)->endOfDay());
        $withinBounds->days();
        $this->assertTrue($withinBounds->isBucketableByDay());

        $beyondBounds = new DateRange($from, $from->copy()->addDays(DateRange::MAX_DAYS)->endOfDay());
        $this->assertFalse($beyondBounds->isBucketableByDay());
        $this->expectException(InvalidArgumentException::class);
        $beyondBounds->days();
    }
}
