# frozen_string_literal: true

require 'spec_helper'

module Calagator
  describe Source::Parser::Ical, type: :model do
    around do |example|
      Timecop.freeze('2000-01-01') do
        example.run
      end
    end

    def events_from_ical_at(filename)
      url = 'http://foo.bar/'
      source = Calagator::Source.new(title: 'Calendar event feed', url: url)
      stub_request(:get, url).to_return(body: read_sample(filename))
      source.to_events
    end

    describe 'in general' do
      it 'reads http URLs as-is' do
        url = 'http://foo.bar/'
        stub_request(:get, url).to_return(body: '42')
        expect(described_class.read_url(url)).to eq '42'
      end

      it 'reads webcal URLs as http' do
        webcal_url = 'webcal://foo.bar/'
        http_url   = 'http://foo.bar/'
        stub_request(:get, http_url).to_return(body: '42')
        expect(described_class.read_url(webcal_url)).to eq '42'
      end
    end

    describe 'when parsing events and their venues' do
      before do
        url = 'http://foo.bar/'
        stub_request(:get, url).to_return(body: read_sample('ical_upcoming_many.ics'))
        @events = Source::Parser.to_events(url: url)
      end

      it 'venues should be' do
        @events.each do |event|
          expect(event.venue).not_to be_nil
        end
      end
    end

    describe 'when parsing multiple items in an Eventful feed' do
      before do
        url = 'http://foo.bar/'
        stub_request(:get, url).to_return(body: read_sample('ical_eventful_many.ics'))
        @events = Source::Parser.to_events(url: url)
      end

      it 'finds multiple events' do
        expect(@events.size).to eq 15
      end

      it 'finds venues for events' do
        @events.each do |event|
          expect(event.venue.title).not_to be_nil
        end
      end

      it 'matches each event with its venue' do
        expect(@events.map { |event| [event.title, event.venue.street_address] }).to eq [
          ['iMovie and iDVD Workshop', '7293 SW Bridgeport Road'],
          ['iMovie and iDVD Workshop', '700 Southwest Fifth Avenue Suite #1035'],
          ['Portland Macintosh Users Group (PMUG)', 'Jean Vollum Natural Capital Center'],
          ['Morning Meetings: IT', '622 SE Grand Avenue'],
          ["Portland Python Users' Group", '622 SE Grand Avenue'],
          ['Computer Basics Class', '12375 SW Fifth Street'],
          ["Code 'n' Splode", '622 SE Grand Avenue'],
          ['Google Analytics Seminars for Success in Portland,OR', '310 SW Lincoln Street'],
          ['PDXPHP Monthly Meeting', '1731 SE Tenth Avenue'],
          ['Portland Ruby Brigade', '622 SE Grand Avenue'],
          ['Portland Cisco Router Training: 2-Day Hands-On Seminar', '15525 NW Gateway Court'],
          ['Post Card & Souvenir Distributors Association Convention & Tra de Show', '1000 NE Multnomah'],
          ['Portland Cisco ASA Training:  2-Day Hands-On Seminar', '15525 NW Gateway Court'],
          ['Mythbusters', 'Southwest Broadway at Main Street'],
          ['Wood Technology Clinic & Show', '777 NE Martin Luther King Jr Boulevard']
        ]
      end
    end

    describe 'with iCalendar events' do
      it 'parses Apple iCalendar v3 format' do
        events = events_from_ical_at('ical_apple_v3.ics')

        expect(events.size).to eq 1
        event = events.first
        expect(event.title).to eq 'Coffee with Jason'
        expect(event.start_time).to eq Time.zone.parse('2010-04-08 00:00:00 PDT -07:00')
        expect(event.end_time).to eq Time.zone.parse('2010-04-08 01:00:00 PDT -07:00')
        expect(event.venue).to be_nil
      end

      it 'parses basic iCalendar format' do
        events = events_from_ical_at('ical_basic.ics')

        expect(events.size).to eq 1
        event = events.first
        expect(event.title).to be_blank
        expect(event.start_time).to eq Time.zone.parse('Wed Jan 17 00:00:00 2007')
        expect(event.venue).to be_nil
      end

      it 'parses basic iCalendar format with a duration and set the correct end time' do
        events = events_from_ical_at('ical_basic_with_duration.ics')

        expect(events.size).to eq 1
        event = events.first
        expect(event.title).to be_blank
        expect(event.start_time).to eq Time.zone.parse('2010-04-08 00:00:00')
        expect(event.end_time).to eq Time.zone.parse('2010-04-08 01:00:00')
        expect(event.venue).to be_nil
      end

      it 'parses Google iCalendar feed with multiple events' do
        events = events_from_ical_at('ical_google.ics')
        # TODO: add specs for venues/locations

        expect(events.size).to eq 47

        event = events.first
        expect(event.title).to eq 'XPDX (eXtreme Programming) at CubeSpace'
        expect(event.description).to be_blank
        expect(event.start_time).to eq Time.parse('2007-10-24 18:30:00 PDT').in_time_zone
        expect(event.end_time).to eq Time.parse('2007-10-24 19:30:00 PDT').in_time_zone

        event = events[17]
        expect(event.title).to eq 'Code Sprint/Coding Dojo at CubeSpace'
        expect(event.description).to be_blank
        expect(event.start_time).to eq Time.parse('2007-10-17 19:00:00 PDT').in_time_zone
        expect(event.end_time).to eq Time.parse('2007-10-17 21:00:00 PDT').in_time_zone

        event = events.last
        expect(event.title).to eq 'Adobe Developer User Group'
        expect(event.description).to eq 'http://pdxria.com/'
        expect(event.start_time).to eq Time.parse('2007-01-16 17:30:00 PST').in_time_zone
        expect(event.end_time).to eq Time.parse('2007-01-16 18:30:00 PST').in_time_zone
      end

      it 'parses non-Vcard locations' do
        events = events_from_ical_at('ical_google.ics')
        expect(events.first.venue.title).to eq 'CubeSpace'
      end

      it 'parses a calendar file with multiple calendars' do
        events = events_from_ical_at('ical_multiple_calendars.ics')
        expect(events.size).to eq 3
        expect(events.map(&:title)).to eq ['Coffee with Jason', 'Coffee with Mike', 'Coffee with Kim']
      end

      it 'does not swallow errors' do
        expect(RiCal).to receive(:parse_string).and_raise(TypeError)
        expect { events_from_ical_at('ical_multiple_calendars.ics') }.to raise_error(TypeError)
      end
    end

    describe 'when importing events with non-local times' do
      it 'stores time ending in Z as UTC' do
        url = 'http://foo.bar/'
        stub_request(:get, url).to_return(body: read_sample('ical_z.ics'))
        @source = Source.new(title: 'Non-local time', url: url)
        events = @source.create_events!
        event = events.first

        expect(event.start_time).to eq Time.parse('Thu Jul 01 08:00:00 +0000 2010').in_time_zone
        expect(event.end_time).to eq Time.parse('Thu Jul 01 09:00:00 +0000 2010').in_time_zone

        # time should be the same after saving event to, and getting it from, database
        event.save
        event.reload
        expect(event.start_time).to eq Time.parse('Thu Jul 01 08:00:00 +0000 2010').in_time_zone
        expect(event.end_time).to eq Time.parse('Thu Jul 01 09:00:00 +0000 2010').in_time_zone
      end

      it 'stores time with TZID=GMT in UTC' do
        events = events_from_ical_at('ical_gmt.ics')
        expect(events.size).to eq 1
        event = events.first
        expect(event.start_time).to eq Time.parse('Fri May 07 08:00:00 +0000 2020').in_time_zone
        expect(event.end_time).to eq Time.parse('Fri May 07 09:00:00 +0000 2020').in_time_zone
      end
    end

    describe 'when skipping old events' do
      before do
        url = 'http://foo.bar/'
        stub_request(:get, url).to_return(body:
                                          %(BEGIN:VCALENDAR
X-WR-CALNAME;VALUE=TEXT:NERV
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//nerv.go.jp//iCal 1.0//EN
X-WR-TIMEZONE;VALUE=TEXT:US/Eastern
BEGIN:VEVENT
UID:Unit-01
SUMMARY:Past start and no end
DESCRIPTION:Ayanami
DTSTART:#{(Time.now.in_time_zone - 1.year).strftime('%Y%m%d')}
DTSTAMP:040425
SEQ:0
END:VEVENT
BEGIN:VEVENT
UID:Unit-02
SUMMARY:Current start and no end
DESCRIPTION:Soryu
DTSTART:#{(Time.now.in_time_zone + 1.year).strftime('%Y%m%d')}
DTSTAMP:040425
SEQ:1
END:VEVENT
BEGIN:VEVENT
UID:Unit-03
SUMMARY:Past start and current end
DESCRIPTION:Soryu a
DTSTART:#{(Time.now.in_time_zone - 1.year).strftime('%Y%m%d')}
DTEND:#{(Time.now.in_time_zone + 1.year).strftime('%Y%m%d')}
DTSTAMP:040425
END:VEVENT
BEGIN:VEVENT
UID:Unit-04
SUMMARY:Current start and current end
DESCRIPTION:Soryu as
DTSTART:#{Time.now.in_time_zone.strftime('%Y%m%d')}
DTEND:#{(Time.now.in_time_zone + 1.year).strftime('%Y%m%d')}
DTSTAMP:040425
END:VEVENT
BEGIN:VEVENT
UID:Unit-05
SUMMARY:Past start and past end
DESCRIPTION:Soryu qewr
DTSTART:#{(Time.now.in_time_zone - 1.year).strftime('%Y%m%d')}
DTEND:#{(Time.now.in_time_zone - 1.year).strftime('%Y%m%d')}
DTSTAMP:040425
END:VEVENT
BEGIN:VEVENT
UID:Unit-06
SUMMARY:Current start and past end
DESCRIPTION:Not a valid event
DTSTART:#{Time.now.in_time_zone.strftime('%Y%m%d')}
DTEND:#{(Time.now.in_time_zone - 1.year).strftime('%Y%m%d')}
DTSTAMP:040425
END:VEVENT
END:VCALENDAR))
        @source = Source.new(title: 'Title', url: url)
      end

      it 'is able to skip invalid and old events' do
        events = @source.create_events!
        expect(events.map(&:title)).to eq [
          'Current start and no end',
          'Past start and current end',
          'Current start and current end'
        ]
      end
    end

    describe 'when parsing an invalid ical' do
      before do
        url = 'http://foo.bar/'
        stub_request(:get, url).to_return(body:
                                          %(BEGIN:VCALENDAR
BEGIN:VEVENT
OMGWTFBBQ
END:VCALENDAR))
        @source = Source.new(title: 'Title', url: url)
      end

      it 'returns no events' do
        @source.create_events!.should == []
      end
    end
  end
end
