# frozen_string_literal: true

require "net/http"
require "net/https"
require "open-uri"

# == Source::Parser
#
# A hierarchy of classes that provide a way to parse different source formats and return hCalendar events.
# The base class for all format-specific parsers. Do not use instances of this class
# directly, use a subclass of Parser to do the parsing instead.
module Calagator
  class Source::Parser < Struct.new(:url, :source)
    # Return an Array of unsaved Event instances.
    def self.to_events(url: nil, source: nil)
      # Return events from the first parser that succeeds
      events = matched_parsers(url).lazy.collect do |parser|
        parser.new(url, source).to_events
      end.detect(&:present?)

      events || []
    end

    def self.matched_parsers(url)
      # start with the parser that matches the given URL
      parsers.sort_by do |parser|
        match = parser.url_pattern.present? && url.try(:match, parser.url_pattern)
        match ? 0 : 1
      end
    end
    private_class_method :matched_parsers

    cattr_accessor(:parsers) { Set.new }

    def self.inherited(subclass)
      parsers << subclass
    end

    class_attribute :label, :url_pattern

    # Returns an Array of sorted string labels for the parsers.
    def self.labels
      parsers.map { |p| p.label.to_s }.sort_by(&:downcase)
    end

    def self.read_url(url)
      RestClient.get(url).to_str
    rescue RestClient::Unauthorized
      raise Source::Parser::HttpAuthenticationRequiredError
    end

    def to_events
      raise NotImplementedError
    end

    def self.<=>(other)
      # use site-specific parsers first, then generics alphabetically
      if url_pattern && !other.url_pattern
        -1
      elsif !url_pattern && other.url_pattern
        1
      else
        label <=> other.label
      end
    end

    private

    def event_or_duplicate(event)
      duplicates = event.find_exact_duplicates
      if duplicates.present?
        duplicates.first.originator
      else
        event
      end
    end

    def venue_or_duplicate(venue)
      duplicates = venue.find_exact_duplicates
      if duplicates.present?
        duplicates.first.originator
      else
        venue_machine_tag_name = venue.tag_list.find do |tag_name|
          MachineTag.new(tag_name).venue?
        end
        matched_venue = Venue.tagged_with(venue_machine_tag_name).first

        if matched_venue.present?
          matched_venue.originator
        else
          venue
        end
      end
    end

    def to_events_api_helper(url, error_key = "error")
      # Extract +event_id+ from :url using +url_pattern+.
      event_id = url[self.class.url_pattern, 1]
      return false unless event_id # Give up unless we find the identifier.

      # Get URL and params for using the API.
      url, params = *yield(event_id)

      # Get data from the API.
      data = RestClient.get(url, params: params, accept: "json").to_str
      data = JSON.parse(data)

      # Stop if API tells us there's an error.
      raise Source::Parser::NotFound, error if data[error_key]
      data["event_id"] = event_id
      data
    end
  end
end

require_relative "parser/not_found"
require_relative "parser/ical"
require_relative "parser/hcal"
