/*
 * Copyright 2014-2019 michael-simons.eu.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ac.simons.biking2.bikingpictures;

import ac.simons.biking2.config.DatastoreConfig;
import ac.simons.biking2.bikingpictures.rss.RSSDateTimeAdapter;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URLConnection;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.List;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

/**
 * @author Michael J. Simons
 */
class FetchBikingPicturesJobTest {

    private final RSSDateTimeAdapter dateTimeAdapter = new RSSDateTimeAdapter();
    private final File tmpDir;

    FetchBikingPicturesJobTest() {
        this.tmpDir = new File(System.getProperty("java.io.tmpdir"), Long.toString(System.currentTimeMillis()));
        this.tmpDir.deleteOnExit();
    }

    @Test
    void jobShouldAbortWhenOlderItemsThanMaxPubDateAreFoundOnPage1() throws Exception {
        final DailyFratzeProvider dailyFratzeProvider = mock(DailyFratzeProvider.class);
        when(dailyFratzeProvider.getRSSConnection(null)).thenReturn(this.getClass().getResource("/biking_pictures.rss").openConnection());

        final BikingPictureRepository bikingPictureRepository = mock(BikingPictureRepository.class);
        when(bikingPictureRepository.getMaxPubDate()).thenReturn(dateTimeAdapter.unmarshal("Sat, 07 Sep 2013 18:43:48 GMT").toOffsetDateTime());

        final FetchBikingPicturesJob job = new FetchBikingPicturesJob(dailyFratzeProvider, bikingPictureRepository, tmpDir);

        final List<BikingPictureEntity> toDownload = job.createDownloadList();

        assertEquals(5, toDownload.size());
        verify(dailyFratzeProvider).getRSSConnection(null);
    }

    @Test
    void jobShouldAbortWhenOlderItemsThanMaxPubDateAreFoundOnPage2() throws Exception {
        final DailyFratzeProvider dailyFratzeProvider = mock(DailyFratzeProvider.class);
        when(dailyFratzeProvider.getRSSConnection(null)).thenReturn(this.getClass().getResource("/biking_pictures.rss").openConnection());
        final String page2 = "http://dailyfratze.de/michael/tags/Theme/Radtour?format=rss&dir=desc&page=2";
        when(dailyFratzeProvider.getRSSConnection(page2)).thenReturn(this.getClass().getResource("/biking_pictures.2.rss").openConnection());
        final BikingPictureRepository bikingPictureRepository = mock(BikingPictureRepository.class);
        when(bikingPictureRepository.getMaxPubDate()).thenReturn(dateTimeAdapter.unmarshal("Sun, 08 May 2011 18:38:25 GMT").toOffsetDateTime());

        final FetchBikingPicturesJob job = new FetchBikingPicturesJob(dailyFratzeProvider, bikingPictureRepository, tmpDir);

        final List<BikingPictureEntity> toDownload = job.createDownloadList();

        assertEquals(46, toDownload.size());
        verify(dailyFratzeProvider).getRSSConnection(null);
        verify(dailyFratzeProvider).getRSSConnection(page2);
    }

    @Test
    void jobShouldHandleExceptionsGracefully() throws MalformedURLException, IOException, Exception {
        final DailyFratzeProvider dailyFratzeProvider = mock(DailyFratzeProvider.class);
        when(dailyFratzeProvider.getRSSConnection(null)).thenReturn(new File("/i/dont/exists").toURI().toURL().openConnection());

        final BikingPictureRepository bikingPictureRepository = mock(BikingPictureRepository.class);
        when(bikingPictureRepository.getMaxPubDate()).thenReturn(dateTimeAdapter.unmarshal("Sun, 08 May 2011 18:38:25 GMT").toOffsetDateTime());

        final FetchBikingPicturesJob job = new FetchBikingPicturesJob(dailyFratzeProvider, bikingPictureRepository, tmpDir);
        final List<BikingPictureEntity> toDownload = job.createDownloadList();
        assertEquals(0, toDownload.size());
    }

    @Test
    void jobShouldThrowExceptionOnInvalidStorageDir() throws IOException {
        final DailyFratzeProvider dailyFratzeProvider = mock(DailyFratzeProvider.class);
        final BikingPictureRepository bikingPictureRepository = mock(BikingPictureRepository.class);

        final File someFile = new File(System.getProperty("java.io.tmpdir"), "deleteMeReally");
        someFile.deleteOnExit();
        assertTrue(someFile.createNewFile());

        var msg = Assertions.assertThrows(
                BikingPicturesStorageException.class,
                () -> new FetchBikingPicturesJob(dailyFratzeProvider, bikingPictureRepository, someFile)).getMessage();
        Assertions.assertEquals("Could not create bikingPicturesStorage!", msg);
    }

    @Test
    void jobShouldAbortWhenNoMorePagesAreAvailable() throws Exception {
        final DailyFratzeProvider dailyFratzeProvider = mock(DailyFratzeProvider.class);
        when(dailyFratzeProvider.getRSSConnection(null)).thenReturn(this.getClass().getResource("/biking_pictures.rss").openConnection());
        final String page2 = "http://dailyfratze.de/michael/tags/Theme/Radtour?format=rss&dir=desc&page=2";
        when(dailyFratzeProvider.getRSSConnection(page2)).thenReturn(this.getClass().getResource("/biking_pictures.2.rss").openConnection());
        final BikingPictureRepository bikingPictureRepository = mock(BikingPictureRepository.class);
        when(bikingPictureRepository.getMaxPubDate()).thenReturn(ZonedDateTime.of(2000, 1, 1, 21, 21, 00, 0, ZoneId.systemDefault()).toOffsetDateTime());
        final FetchBikingPicturesJob job = new FetchBikingPicturesJob(dailyFratzeProvider, bikingPictureRepository, tmpDir);

        final List<BikingPictureEntity> toDownload = job.createDownloadList();

        assertEquals(48, toDownload.size());
        verify(dailyFratzeProvider).getRSSConnection(null);
        verify(dailyFratzeProvider).getRSSConnection(page2);
        verify(dailyFratzeProvider, times(0)).getRSSConnection("http://dailyfratze.de/michael/tags/Theme/Radtour?format=rss&dir=desc&page=3");
    }

    @Test
    void shouldDownloadStuff() throws Exception {

        final DailyFratzeProvider dailyFratzeProvider = mock(DailyFratzeProvider.class);
        when(dailyFratzeProvider.getImageConnection(45644)).thenReturn(this.getClass().getResource("/45644.jpg").openConnection());
        when(dailyFratzeProvider.getImageConnection(45325)).thenReturn(this.getClass().getResource("/45325.jpg").openConnection());

        // Failure on connection
        URLConnection mockConnection = mock(URLConnection.class);
        when(mockConnection.getInputStream()).thenThrow(new IOException("ignoreMe"));
        when(dailyFratzeProvider.getImageConnection(44142)).thenReturn(mockConnection);

        when(dailyFratzeProvider.getImageConnection(43461)).thenReturn(null);

        // One picture exists
        final BikingPictureEntity existingPicture = new BikingPictureEntity("http://dailyfratze.de/fratzen/m/45325.jpg", dateTimeAdapter.unmarshal("Mon, 30 Dec 2013 23:18:35 GMT"), "http://dailyfratze.de/michael/2013/12/30");
        final BikingPictureRepository bikingPictureRepository = mock(BikingPictureRepository.class);
        when(bikingPictureRepository.findByExternalId(45325)).thenReturn(existingPicture);

        final FetchBikingPicturesJob job = new FetchBikingPicturesJob(dailyFratzeProvider, bikingPictureRepository, tmpDir);

        final List<BikingPictureEntity> downloadList = Arrays.asList(new BikingPictureEntity("http://dailyfratze.de/fratzen/m/45644.jpg", dateTimeAdapter.unmarshal("Sun, 12 Jan 2014 21:40:25 GMT"), "http://dailyfratze.de/michael/2014/1/12"),
                existingPicture,
                new BikingPictureEntity("http://dailyfratze.de/fratzen/m/44142.jpg", dateTimeAdapter.unmarshal("Sun, 03 Nov 2013 21:01:01 GMT"), "http://dailyfratze.de/michael/2013/11/3"),
                new BikingPictureEntity("http://dailyfratze.de/fratzen/m/43461.jpg", dateTimeAdapter.unmarshal("Sat, 05 Oct 2013 18:13:20 GMT"), "http://dailyfratze.de/michael/2013/10/3")
        );

        final List<BikingPictureEntity> downloaded = job.download(downloadList);

        assertEquals(1, downloaded.size());
        assertTrue(new File(this.tmpDir, DatastoreConfig.BIKING_PICTURES_DIRECTORY + "/45644.jpg").isFile());
        assertFalse(new File(this.tmpDir, DatastoreConfig.BIKING_PICTURES_DIRECTORY + "/45325.jpg").isFile());
        assertFalse(new File(this.tmpDir, DatastoreConfig.BIKING_PICTURES_DIRECTORY + "/44142.jpg").isFile());
        assertFalse(new File(this.tmpDir, DatastoreConfig.BIKING_PICTURES_DIRECTORY + "/43461.jpg").isFile());
        // Verify number of calls with arbitrary ints
        verify(dailyFratzeProvider, times(3)).getImageConnection(anyInt());
    }

    @Test
    void runMethodShouldWorkAsExpected() throws IOException {
        final DailyFratzeProvider dailyFratzeProvider = mock(DailyFratzeProvider.class);
        when(dailyFratzeProvider.getRSSConnection(null)).thenReturn(this.getClass().getResource("/biking_pictures.rss").openConnection());

        final BikingPictureRepository bikingPictureRepository = mock(BikingPictureRepository.class);
        when(bikingPictureRepository.getMaxPubDate()).thenReturn(OffsetDateTime.now());

        final FetchBikingPicturesJob job = new FetchBikingPicturesJob(dailyFratzeProvider, bikingPictureRepository, tmpDir);
        job.run();

        verify(dailyFratzeProvider).getRSSConnection(null);
        verify(bikingPictureRepository).getMaxPubDate();

        Mockito.verifyNoMoreInteractions(dailyFratzeProvider, bikingPictureRepository);
    }
}
