<?php
/**
 * Copyright 2012-2020 Nick Korbel
 *
 * This file is part of Booked Scheduler.
 *
 * Booked Scheduler is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Booked Scheduler is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Booked Scheduler.  If not, see <http://www.gnu.org/licenses/>.
 */

require_once(ROOT_DIR . 'lib/Application/Reporting/namespace.php');
require_once(ROOT_DIR . 'Domain/Access/namespace.php');

class ReportingServiceTests extends TestBase
{
    /**
     * @var ReportingService
     */
    private $rs;

    /**
     * @var FakeReportingRepository
     */
    private $reportingRepository;
    /**
     * @var FakeScheduleRepository
     */
    private $scheduleRepository;

    public function setUp(): void
    {
        parent::setup();

        $this->reportingRepository = new FakeReportingRepository();

        $this->scheduleRepository = new FakeScheduleRepository();
        $this->rs = new ReportingService($this->reportingRepository, new FakeAttributeRepository(), $this->scheduleRepository);
    }

    public function testBuildsCustomResourceReport()
    {
        $start = '2010-01-01';
        $end = '2010-01-02';

        $resourceId = array(1);
        $scheduleId = array(2);
        $userId = 3;
        $groupId = array(4);
        $accessoryId = array(5);
        $participantId = 6;
        $resourceTypeId = array(7);

        $usage = new Report_Usage(Report_Usage::RESOURCES);
        $selection = new Report_ResultSelection(Report_ResultSelection::FULL_LIST);
        $groupBy = new Report_GroupBy(Report_GroupBy::GROUP);
        $range = new Report_Range(Report_Range::DATE_RANGE, $start, $end, 'UTC');
        $filter = new Report_Filter($resourceId, $scheduleId, $userId, $groupId, $accessoryId, $participantId, true, $resourceTypeId);

        $commandBuilder = new ReportCommandBuilder();
        $commandBuilder->SelectFullList()
            ->OfResources()
            ->Within(Date::Parse($start, 'UTC'), Date::Parse($end, 'UTC'))
            ->WithResourceIds($resourceId)
            ->WithUserId($userId)
            ->WithParticipantId($participantId)
            ->WithScheduleIds($scheduleId)
            ->WithGroupIds($groupId)
            ->WithAccessoryIds($accessoryId)
            ->WithResourceTypeIds($resourceTypeId)
            ->WithDeleted()
            ->GroupByGroup();

        $rows = array(array(
            ColumnNames::OWNER_FIRST_NAME => 'value',
            ColumnNames::OWNER_LAST_NAME => 'value',
            ColumnNames::OWNER_USER_ID => 'value',
        ));

        $this->reportingRepository->_CustomReportData = $rows;

        $report = $this->rs->GenerateCustomReport($usage, $selection, $groupBy, $range, $filter, $this->fakeUser->Timezone);

        $cols = new ReportColumns();
        $cols->Add(ColumnNames::OWNER_FIRST_NAME);
        $cols->Add(ColumnNames::OWNER_LAST_NAME);
        $cols->Add(ColumnNames::OWNER_USER_ID);

        $this->assertEquals($cols, $report->GetColumns());
        $this->assertEquals(new CustomReportData($rows), $report->GetData());
    }

    public function testSavesReportForUser()
    {
        $reportName = 'reportName';
        $userId = 12;

        $usage = new Report_Usage(Report_Usage::ACCESSORIES);
        $selection = new Report_ResultSelection(Report_ResultSelection::COUNT);
        $groupBy = new Report_GroupBy(Report_GroupBy::RESOURCE);
        $range = new Report_Range(Report_Range::ALL_TIME, Date::Now(), Date::Now());
        $filter = new Report_Filter(null, null, null, null, null, null, null, null);

        $savedReport = new SavedReport($reportName, $userId, $usage, $selection, $groupBy, $range, $filter);

        $this->rs->Save($reportName, $userId, $usage, $selection, $groupBy, $range, $filter);

        $this->assertEquals($savedReport, $this->reportingRepository->_LastSavedReport);
    }

    public function testGetsSavedReports()
    {
        $reports = array(new FakeSavedReport());
        $userId = 100;

        $this->reportingRepository->_SavedReports = $reports;

        $actualReports = $this->rs->GetSavedReports($userId);

        $this->assertEquals($reports, $actualReports);
    }

    public function testGeneratesSavedReport()
    {
        $reportId = 1;
        $userId = 2;

        $savedReport = new FakeSavedReport();
        $data = array();
        $report = new CustomReport($data, new FakeAttributeRepository());

        $this->reportingRepository->_SavedReport = $savedReport;
        $this->reportingRepository->_CustomReportData = $data;

        $expectedReport = new GeneratedSavedReport($savedReport, $report);
        $actualReport = $this->rs->GenerateSavedReport($reportId, $userId, $this->fakeUser->Timezone);

        $this->assertEquals($expectedReport, $actualReport);
    }

    public function testEmailsReport()
    {
        $report = new GeneratedSavedReport(new FakeSavedReport(), new FakeReport());
        $def = new ReportDefinition($report, null);
        $to = 'email';
        $user = $this->fakeUser;

        $cols = 'cols';

        $expectedMessage = new ReportEmailMessage($report, $def, $to, $user, $cols);

        $this->rs->SendReport($report, $def, $to, $user, $cols);
        $this->assertInstanceOf('ReportEmailMessage', $this->fakeEmailService->_LastMessage);
    }

    public function testDeletesReport()
    {
        $reportId = 1;
        $userId = 2;

        $this->rs->DeleteSavedReport($reportId, $userId);

        $this->assertTrue($this->reportingRepository->_Deleted);
        $this->assertEquals($reportId, $this->reportingRepository->_DeletedReport);
    }

    public function testGetsUtilizationReport_DifferentLayoutPerDay()
    {
        $r1 = 1;
        $s1 = 100;

        $tz = 'America/New_York';

        $thursday = Date::Parse('2018-12-20', $tz);
        $friday = Date::Parse('2018-12-21', $tz);
        $saturday = Date::Parse('2018-12-22', $tz);
        $sunday = Date::Parse('2018-12-23', $tz);
        $monday = Date::Parse('2018-12-24', $tz);
        $tuesday = Date::Parse('2018-12-25', $tz);
        $wednesday = Date::Parse('2018-12-26', $tz);

        $thursdaySlots = $this->HourlyBetween(6, 18, $thursday);
        $fridaySlots = $this->HourlyBetween(3, 22, $friday);
        $saturdaySlots = $this->HourlyBetween(8, 22, $saturday);
        $sundaySlots = $this->HourlyBetween(8, 22, $sunday);
        $mondaySlots = $this->HourlyBetween(8, 20, $monday);
        $tuesdaySlots = $this->HourlyBetween(8, 20, $tuesday);
        $wednesdaySlots = $this->HourlyBetween(8, 20, $wednesday);

        $layout = new FakeScheduleLayout();
        $layout->_Timezone = $tz;
        $layout->_UsesDailyLayouts = true;
        $layout->_AddDailyLayout($thursday, $thursdaySlots);
        $layout->_AddDailyLayout($friday, $fridaySlots);
        $layout->_AddDailyLayout($saturday, $saturdaySlots);
        $layout->_AddDailyLayout($sunday, $sundaySlots);
        $layout->_AddDailyLayout($monday, $mondaySlots);
        $layout->_AddDailyLayout($tuesday, $tuesdaySlots);
        $layout->_AddDailyLayout($wednesday, $wednesdaySlots);

        $this->scheduleRepository->_Layout = $layout;

        $data = array(
            $this->GetUtilizationRow($r1, $s1, $thursday->SetTimeString('12:00'), $saturday->SetTimeString('12:00'), 1, 'r1'),
            $this->GetUtilizationRow($r1, $s1, $thursday->SetTimeString('06:00'), $thursday->SetTimeString('08:00'), 2, 'r1'),
            $this->GetUtilizationRow($r1, $s1, $saturday->SetTimeString('13:00'), $saturday->SetTimeString('15:00'), 1, 'r1'),
            $this->GetUtilizationRow($r1, $s1, $sunday->SetTimeString('13:00'), $sunday->SetTimeString('15:00'), 2, 'r1'),
        );

        $this->reportingRepository->_CustomReportData = $data;

        $report = $this->rs->GenerateCustomReport(new Report_Usage(Report_Usage::RESOURCES), new Report_ResultSelection(Report_ResultSelection::UTILIZATION), new Report_GroupBy(Report_GroupBy::NONE), Report_Range::AllTime(), new Report_Filter(null, null, null, null, null, null, false, null), $this->fakeUser->Timezone);

        $data = $report->GetData()->Rows();

        $format = Resources::GetInstance()->GetDateFormat('general_date');

        /**
         * $availableHoursThursday = 12;
         * $blackoutHoursThursday = 2;
         * $reservedHoursThursday = 6;
         *
         * $availableHoursFriday = 19;
         * $blackoutHoursFriday = 0;
         * $reservedHoursFriday = 19;
         *
         * $availableHoursSaturday = 14;
         * $blackoutHoursSaturday = 0;
         * $reservedHoursSaturday = 6;
         **/

        $this->assertEquals($thursday, $data[0][ColumnNames::DATE]);
        $this->assertEquals(60, $data[0][ColumnNames::UTILIZATION], "6/10");
        $this->assertEquals('r1', $data[0][ColumnNames::RESOURCE_NAME_ALIAS]);
        $this->assertEquals($s1, $data[0][ColumnNames::SCHEDULE_ID]);
        $this->assertEquals($r1, $data[0][ColumnNames::RESOURCE_ID]);

        $this->assertEquals($friday, $data[1][ColumnNames::DATE]);
        $this->assertEquals(100, $data[1][ColumnNames::UTILIZATION], "19/19");

        $this->assertEquals($saturday, $data[2][ColumnNames::DATE]);
        $this->assertEquals(43, $data[2][ColumnNames::UTILIZATION], "6/14");

        $this->assertEquals($sunday, $data[3][ColumnNames::DATE]);
        $this->assertEquals(0, $data[3][ColumnNames::UTILIZATION]);
    }

    public function testGetsUtilizationReport_SameLayoutPerDay()
    {
        $r1 = 1;
        $s1 = 100;

        $tz = 'America/New_York';

        $thursday = Date::Parse('2018-12-20', $tz);
        $friday = Date::Parse('2018-12-21', $tz);
        $saturday = Date::Parse('2018-12-22', $tz);
        $sunday = Date::Parse('2018-12-23', $tz);

        $slots = $this->HourlyBetween(6, 18, $thursday);

        $layout = new FakeScheduleLayout();
        $layout->_Timezone = $tz;
        $layout->_UsesDailyLayouts = false;
        $layout->_Layout = $slots;

        $this->scheduleRepository->_Layout = $layout;

        $data = array(
            $this->GetUtilizationRow($r1, $s1, $thursday->SetTimeString('12:00'), $saturday->SetTimeString('12:00'), 1, 'r1'),
            $this->GetUtilizationRow($r1, $s1, $thursday->SetTimeString('06:00'), $thursday->SetTimeString('08:00'), 2, 'r1'),
            $this->GetUtilizationRow($r1, $s1, $saturday->SetTimeString('13:00'), $saturday->SetTimeString('15:00'), 1, 'r1'),
            $this->GetUtilizationRow($r1, $s1, $sunday->SetTimeString('13:00'), $sunday->SetTimeString('15:00'), 2, 'r1'),
        );

        $this->reportingRepository->_CustomReportData = $data;

        $report = $this->rs->GenerateCustomReport(new Report_Usage(Report_Usage::RESOURCES), new Report_ResultSelection(Report_ResultSelection::UTILIZATION), new Report_GroupBy(Report_GroupBy::NONE), Report_Range::AllTime(), new Report_Filter(null, null, null, null, null, null, false, null), $this->fakeUser->Timezone);

        $data = $report->GetData()->Rows();

        $format = Resources::GetInstance()->GetDateFormat('general_date');

        /**
         * availableHours = 12;
         * $blackoutHoursThursday = 2;
         * $reservedHoursThursday = 6;
         * $reservedHoursSaturday = 8;
         **/

        $this->assertEquals($thursday, $data[0][ColumnNames::DATE]);
        $this->assertEquals(60, $data[0][ColumnNames::UTILIZATION], '6/10');
        $this->assertEquals('r1', $data[0][ColumnNames::RESOURCE_NAME_ALIAS]);
        $this->assertEquals($s1, $data[0][ColumnNames::SCHEDULE_ID]);
        $this->assertEquals($r1, $data[0][ColumnNames::RESOURCE_ID]);

        $this->assertEquals($friday, $data[1][ColumnNames::DATE]);
        $this->assertEquals(100, $data[1][ColumnNames::UTILIZATION]);

        $this->assertEquals($saturday, $data[2][ColumnNames::DATE]);
        $this->assertEquals(67, $data[2][ColumnNames::UTILIZATION], '8/12');

        $this->assertEquals($sunday, $data[3][ColumnNames::DATE]);
        $this->assertEquals(0, $data[3][ColumnNames::UTILIZATION]);
    }

    public function testGetsUtilizationReport_CustomLayout()
    {
        $r1 = 1;
        $s1 = 100;

        $tz = 'America/New_York';

        $thursday = Date::Parse('2018-12-20', $tz);
        $friday = Date::Parse('2018-12-21', $tz);
        $saturday = Date::Parse('2018-12-22', $tz);
        $sunday = Date::Parse('2018-12-23', $tz);

        $layout = new CustomScheduleLayout($tz, $s1, $this->scheduleRepository);

        $this->scheduleRepository->_Layout = $layout;
        $this->scheduleRepository->_AddCustomLayout($thursday,
            array(
                new SchedulePeriod($thursday->SetTimeString('12:00'), $thursday->SetTimeString('14:00')),
                new SchedulePeriod($thursday->SetTimeString('16:00'), $thursday->SetTimeString('18:00')),
            ));
        $this->scheduleRepository->_AddCustomLayout($friday, array());

        $this->scheduleRepository->_AddCustomLayout($saturday,
            array(
                new SchedulePeriod($saturday->SetTimeString('12:00'), $saturday->SetTimeString('14:00')),
                new SchedulePeriod($saturday->SetTimeString('16:00'), $saturday->SetTimeString('18:00')),
            ));


        $data = array(
            $this->GetUtilizationRow($r1, $s1, $thursday->SetTimeString('12:00'), $thursday->SetTimeString('14:00'), 1, 'r1'),
            $this->GetUtilizationRow($r1, $s1, $saturday->SetTimeString('12:00'), $saturday->SetTimeString('14:00'), 1, 'r1'),
            $this->GetUtilizationRow($r1, $s1, $saturday->SetTimeString('16:00'), $saturday->SetTimeString('18:00'), 1, 'r1'),
        );

        $this->reportingRepository->_CustomReportData = $data;

        $report = $this->rs->GenerateCustomReport(new Report_Usage(Report_Usage::RESOURCES), new Report_ResultSelection(Report_ResultSelection::UTILIZATION), new Report_GroupBy(Report_GroupBy::NONE), Report_Range::AllTime(), new Report_Filter(null, null, null, null, null, null, false, null), $this->fakeUser->Timezone);

        $data = $report->GetData()->Rows();

        $format = Resources::GetInstance()->GetDateFormat('general_date');

        $this->assertEquals($thursday, $data[0][ColumnNames::DATE]);
        $this->assertEquals(50, $data[0][ColumnNames::UTILIZATION], "2/4");
        $this->assertEquals('r1', $data[0][ColumnNames::RESOURCE_NAME_ALIAS]);
        $this->assertEquals($s1, $data[0][ColumnNames::SCHEDULE_ID]);
        $this->assertEquals($r1, $data[0][ColumnNames::RESOURCE_ID]);

        $this->assertEquals($friday, $data[1][ColumnNames::DATE]);
        $this->assertEquals(0, $data[1][ColumnNames::UTILIZATION]);

        $this->assertEquals($saturday, $data[2][ColumnNames::DATE]);
        $this->assertEquals(100, $data[2][ColumnNames::UTILIZATION], "4/4");
    }

    /**
     * @param int $resourceId
     * @param int $scheduleId
     * @param Date $start
     * @param Date $end
     * @parma int $type
     * @param string $resourceName
     * @return array
     */
    private function GetUtilizationRow($resourceId, $scheduleId, $start, $end, $type = 1, $resourceName = 'name')
    {
        return array(
            ColumnNames::SCHEDULE_ID => $scheduleId,
            ColumnNames::RESOURCE_ID => $resourceId,
            ColumnNames::RESOURCE_NAME_ALIAS => $resourceName,
            ColumnNames::RESERVATION_START => $start->ToDatabase(),
            ColumnNames::RESERVATION_END => $end->ToDatabase(),
            ColumnNames::UTILIZATION_TYPE => $type,
        );
    }

    /**
     * @param int $firstHour
     * @param int $lastHour
     * @param Date $date
     * @return SchedulePeriod[]
     */
    private function HourlyBetween($firstHour, $lastHour, $date)
    {
        $slots = array();
        if ($firstHour != 0) {
            $slots = array(new NonSchedulePeriod($date->SetTimeString("00:00"), $date->SetTimeString("$firstHour:00", true)));
        }

        for ($i = $firstHour; $i < $lastHour; $i++) {
            $e = $i + 1;
            $slots[] = new SchedulePeriod($date->SetTimeString("$i:00"), $date->SetTimeString("$e:00", true));
        }

        if ($lastHour != 0) {
            $slots[] = new NonSchedulePeriod($date->SetTimeString("$lastHour:00"), $date->SetTimeString('24:00', true));
        }

        return $slots;
    }
}

class FakeReportingRepository implements IReportingRepository
{
    public $_CustomReportData = array();
    public $_LastSavedReport;
    public $_SavedReports;
    public $_SavedReport;
    public $_Deleted;
    public $_DeletedReport;

    /**
     * @param ReportCommandBuilder $commandBuilder
     * @return array
     */
    public function GetCustomReport(ReportCommandBuilder $commandBuilder)
    {
        return $this->_CustomReportData;
    }

    /**
     * @param SavedReport $savedReport
     */
    public function SaveCustomReport(SavedReport $savedReport)
    {
        $this->_LastSavedReport = $savedReport;
    }

    /**
     * @param int $userId
     * @return array|SavedReport[]
     */
    public function LoadSavedReportsForUser($userId)
    {
        return $this->_SavedReports;
    }

    /**
     * @param int $reportId
     * @param int $userId
     * @return SavedReport
     */
    public function LoadSavedReportForUser($reportId, $userId)
    {
        return $this->_SavedReport;
    }

    /**
     * @param int $reportId
     * @param int $userId
     */
    public function DeleteSavedReport($reportId, $userId)
    {
        $this->_Deleted = true;
        $this->_DeletedReport = $reportId;
    }
}
