require 'delayed_job'
require 'delayed_job_active_record'

class TestTrack::Session
  COOKIE_LIFESPAN = 1.year # Used for visitor cookie

  def initialize(controller)
    @controller = controller
  end

  def manage
    yield
  ensure
    manage_cookies!
    notify_unsynced_assignments! if sync_assignments?
    create_alias! if signed_up?
  end

  def visitor_dsl
    @visitor_dsl ||= TestTrack::VisitorDSL.new(visitor)
  end

  def state_hash
    {
      url: TestTrack.url,
      cookieDomain: cookie_domain,
      cookieName: visitor_cookie_name,
      registry: visitor.split_registry,
      assignments: visitor.assignment_json
    }
  end

  def log_in!(identifier_type, identifier_value, opts = {})
    @visitor = TestTrack::Visitor.new if opts[:forget_current_visitor]
    visitor.link_identifier!(identifier_type, identifier_value)
    self.mixpanel_distinct_id = visitor.id
    true
  end

  def sign_up!(identifier_type, identifier_value)
    visitor.link_identifier!(identifier_type, identifier_value)
    @signed_up = true
  end

  private

  attr_reader :controller, :signed_up
  alias signed_up? signed_up

  def visitor
    @visitor ||= TestTrack::Visitor.new(id: cookies[visitor_cookie_name])
  end

  def set_cookie(name, value)
    cookies[name] = {
      value: value,
      domain: cookie_domain,
      secure: request.ssl?,
      httponly: false,
      expires: COOKIE_LIFESPAN.from_now
    }
  end

  def cookie_domain
    @cookie_domain ||= _cookie_domain
  end

  def _cookie_domain
    if bare_ip_address?
      request.host
    elsif fully_qualified_cookie_domain_enabled?
      fully_qualified_domain
    else
      wildcard_domain
    end
  end

  def bare_ip_address?
    request.host.match(Resolv::AddressRegex)
  end

  def fully_qualified_domain
    public_suffix_host.name
  end

  def wildcard_domain
    "." + public_suffix_host.domain
  end

  def public_suffix_host
    @public_suffix_host ||= PublicSuffix.parse(request.host)
  end

  def manage_cookies!
    set_cookie(mixpanel_cookie_name, mixpanel_cookie.to_json)
    set_cookie(visitor_cookie_name, visitor.id)
  end

  def request
    controller.request
  end

  def cookies
    controller.send(:cookies)
  end

  def notify_unsynced_assignments!
    payload = {
      mixpanel_distinct_id: mixpanel_distinct_id,
      visitor_id: visitor.id,
      assignments: visitor.unsynced_assignments
    }
    ActiveSupport::Notifications.instrument('test_track.notify_unsynced_assignments', payload) do
      ##
      # This block creates an unbounded number of threads up to 1 per request.
      # This can potentially cause issues under high load, in which case we should move to a thread pool/work queue.
      Thread.new do
        TestTrack::UnsyncedAssignmentsNotifier.new(payload).notify
      end
    end
  end

  def create_alias!
    create_alias_job = TestTrack::CreateAliasJob.new(
      existing_id: mixpanel_distinct_id,
      alias_id: visitor.id
    )
    Delayed::Job.enqueue(create_alias_job)
  end

  def sync_assignments?
    visitor.loaded? && visitor.unsynced_assignments.present?
  end

  def mixpanel_distinct_id
    mixpanel_cookie['distinct_id']
  end

  def mixpanel_distinct_id=(value)
    mixpanel_cookie['distinct_id'] = value
  end

  def mixpanel_cookie
    @mixpanel_cookie ||= read_mixpanel_cookie || generate_mixpanel_cookie
  end

  def read_mixpanel_cookie
    mixpanel_cookie = cookies[mixpanel_cookie_name]
    begin
      JSON.parse(mixpanel_cookie) if mixpanel_cookie
    rescue JSON::ParserError
      Rails.logger.error("malformed mixpanel JSON from cookie #{URI.unescape(mixpanel_cookie)}")
      nil
    end
  end

  def generate_mixpanel_cookie
    { 'distinct_id' => visitor.id }
  end

  def mixpanel_token
    ENV['MIXPANEL_TOKEN'] || raise("ENV['MIXPANEL_TOKEN'] must be set")
  end

  def mixpanel_cookie_name
    "mp_#{mixpanel_token}_mixpanel"
  end

  def visitor_cookie_name
    ENV['TEST_TRACK_VISITOR_COOKIE_NAME'] || 'tt_visitor_id'
  end

  def fully_qualified_cookie_domain_enabled?
    ENV['TEST_TRACK_FULLY_QUALIFIED_COOKIE_DOMAIN_ENABLED'] == '1'
  end
end