# frozen_string_literal: true

RSpec.describe TopicLinkClick do
  it { is_expected.to belong_to :topic_link }
  it { is_expected.to belong_to :user }
  it { is_expected.to validate_presence_of :topic_link_id }

  def test_uri
    URI.parse("http://test.host")
  end

  describe "topic_links" do
    before do
      @topic = Fabricate(:topic, user: Fabricate(:user, refresh_auto_groups: true))
      @post = Fabricate(:post_with_external_links, user: @topic.user, topic: @topic)
      TopicLink.extract_from(@post)
      @topic_link = @topic.topic_links.first
    end

    it "has 0 clicks at first" do
      expect(@topic_link.clicks).to eq(0)
    end

    describe ".create" do
      before { TopicLinkClick.create(topic_link: @topic_link, ip_address: "192.168.1.1") }

      it "creates the forum topic link click" do
        expect(TopicLinkClick.count).to eq(1)

        @topic_link.reload
        expect(@topic_link.clicks).to eq(1)

        expect(TopicLinkClick.first.ip_address.to_s).to eq("192.168.1.1")
      end
    end

    describe ".create_from" do
      it "works correctly" do
        # returns nil to prevent exploits
        click =
          TopicLinkClick.create_from(
            url: "http://url-that-doesnt-exist.com",
            post_id: @post.id,
            ip: "127.0.0.1",
          )
        expect(click).to eq(nil)

        # redirects if allowlisted
        click =
          TopicLinkClick.create_from(
            url: "https://www.youtube.com/watch?v=jYd_5aggzd4",
            post_id: @post.id,
            ip: "127.0.0.1",
          )
        expect(click).to eq("https://www.youtube.com/watch?v=jYd_5aggzd4")

        # does not change own link
        expect {
          TopicLinkClick.create_from(
            url: @topic_link.url,
            post_id: @post.id,
            ip: "127.0.0.0",
            user_id: @post.user_id,
          )
        }.not_to change(TopicLinkClick, :count)

        # can handle double # in a url
        # NOTE: this is not compliant but exists in the wild
        click =
          TopicLinkClick.create_from(
            url: "http://discourse.org#a#b",
            post_id: @post.id,
            ip: "127.0.0.1",
          )
        expect(click).to eq("http://discourse.org#a#b")
      end

      context "with a valid url and post_id" do
        before do
          @url =
            TopicLinkClick.create_from(url: @topic_link.url, post_id: @post.id, ip: "127.0.0.1")
          @click = TopicLinkClick.last
        end

        it "creates a click" do
          expect(@click).to be_present
          expect(@click.topic_link).to eq(@topic_link)
          expect(@url).to eq(@topic_link.url)

          # second click should not record
          expect {
            TopicLinkClick.create_from(url: @topic_link.url, post_id: @post.id, ip: "127.0.0.1")
          }.not_to change(TopicLinkClick, :count)
        end
      end

      context "while logged in" do
        fab!(:other_user) { Fabricate(:user) }
        before do
          @url =
            TopicLinkClick.create_from(
              url: @topic_link.url,
              post_id: @post.id,
              ip: "127.0.0.1",
              user_id: other_user.id,
            )
          @click = TopicLinkClick.last
        end

        it "creates a click without an IP" do
          expect(@click).to be_present
          expect(@click.topic_link).to eq(@topic_link)
          expect(@click.user_id).to eq(other_user.id)
          expect(@click.ip_address).to eq(nil)
        end
      end

      context "with relative urls" do
        let(:host) { URI.parse(Discourse.base_url).host }

        it "returns the url" do
          url = TopicLinkClick.create_from(url: "/relative-url", post_id: @post.id, ip: "127.0.0.1")
          expect(url).to eq("/relative-url")
        end

        it "finds a protocol relative urls with a host" do
          url = "//#{host}/relative-url"
          redirect = TopicLinkClick.create_from(url: url)
          expect(redirect).to eq(url)
        end

        it "returns the url if it's on our host" do
          url = "http://#{host}/relative-url"
          redirect = TopicLinkClick.create_from(url: url)
          expect(redirect).to eq(url)
        end

        context "with cdn links" do
          before do
            Rails.configuration.action_controller.asset_host = "https://cdn.discourse.org/stuff"
          end

          after { Rails.configuration.action_controller.asset_host = nil }

          it "correctly handles cdn links" do
            url =
              TopicLinkClick.create_from(
                url: "https://cdn.discourse.org/stuff/my_link",
                topic_id: @topic.id,
                ip: "127.0.0.3",
              )

            expect(url).to eq("https://cdn.discourse.org/stuff/my_link")

            # cdn exploit
            url =
              TopicLinkClick.create_from(
                url: "https://cdn.discourse.org/bad/my_link",
                topic_id: @topic.id,
                ip: "127.0.0.3",
              )

            expect(url).to eq(nil)

            # cdn better link track
            path = "/uploads/site/29/5b585f848d8761d5.xls"

            post = Fabricate(:post, topic: @topic, raw: "[test](#{path})")
            TopicLink.extract_from(post)

            url =
              TopicLinkClick.create_from(
                url: "https://cdn.discourse.org/stuff#{path}",
                topic_id: post.topic_id,
                post_id: post.id,
                ip: "127.0.0.3",
              )

            expect(url).to eq("https://cdn.discourse.org/stuff#{path}")

            click = TopicLinkClick.order("id desc").first

            expect(click.topic_link_id).to eq(TopicLink.order("id desc").first.id)
          end
        end

        context "with s3 cdns" do
          it "works with s3 urls" do
            setup_s3
            SiteSetting.s3_cdn_url = "https://discourse-s3-cdn.global.ssl.fastly.net"

            post =
              Fabricate(
                :post,
                topic: @topic,
                raw: "[test](//test.localhost/uploads/default/my-test-link)",
              )
            TopicLink.extract_from(post)

            url =
              TopicLinkClick.create_from(
                url: "https://discourse-s3-cdn.global.ssl.fastly.net/my-test-link",
                topic_id: @topic.id,
                ip: "127.0.0.3",
              )

            expect(url).to be_present
          end
        end
      end

      context "with a HTTPS version of the same URL" do
        before do
          @url =
            TopicLinkClick.create_from(
              url: "https://twitter.com",
              topic_id: @topic.id,
              ip: "127.0.0.3",
            )
          @click = TopicLinkClick.last
        end

        it "creates a click" do
          expect(@click).to be_present
          expect(@click.topic_link).to eq(@topic_link)
          expect(@url).to eq("https://twitter.com")
        end
      end

      context "with a google analytics tracking code" do
        before do
          @url =
            TopicLinkClick.create_from(
              url: "http://twitter.com?_ga=1.16846778.221554446.1071987018",
              topic_id: @topic.id,
              ip: "127.0.0.3",
            )
          @click = TopicLinkClick.last
        end

        it "creates a click" do
          expect(@click).to be_present
          expect(@click.topic_link).to eq(@topic_link)
          expect(@url).to eq("http://twitter.com?_ga=1.16846778.221554446.1071987018")
        end
      end

      context "with a query param and google analytics" do
        before do
          @topic = Fabricate(:topic, user: Fabricate(:user, refresh_auto_groups: true))
          @post =
            Fabricate(
              :post,
              topic: @topic,
              user: @topic.user,
              raw: "Here's a link to twitter: http://twitter.com?ref=forum",
            )
          TopicLink.extract_from(@post)
          @topic_link = @topic.topic_links.first
        end

        it "creates a click" do
          url =
            TopicLinkClick.create_from(
              url: "http://twitter.com?ref=forum&_ga=1.16846778.221554446.1071987018",
              topic_id: @topic.id,
              post_id: @post.id,
              ip: "127.0.0.3",
            )
          click = TopicLinkClick.last
          expect(click).to be_present
          expect(click.topic_link).to eq(@topic_link)
          expect(url).to eq("http://twitter.com?ref=forum&_ga=1.16846778.221554446.1071987018")
        end
      end

      context "with same base URL with different query" do
        it "are handled differently" do
          post = Fabricate(:post, raw: <<~RAW)
            no query param: http://example.com/a
            with query param: http://example.com/a?b=c
            with two query params: http://example.com/a?b=c&d=e
          RAW

          TopicLink.extract_from(post)

          TopicLinkClick.create_from(
            url: "http://example.com/a",
            post_id: post.id,
            ip: "127.0.0.1",
            user: Fabricate(:user),
          )
          TopicLinkClick.create_from(
            url: "http://example.com/a?b=c",
            post_id: post.id,
            ip: "127.0.0.2",
            user: Fabricate(:user),
          )
          TopicLinkClick.create_from(
            url: "http://example.com/a?b=c&d=e",
            post_id: post.id,
            ip: "127.0.0.3",
            user: Fabricate(:user),
          )
          TopicLinkClick.create_from(
            url: "http://example.com/a?b=c",
            post_id: post.id,
            ip: "127.0.0.4",
            user: Fabricate(:user),
          )

          expect(
            TopicLink.where("url LIKE '%example.com%'").pluck(:url, :clicks),
          ).to contain_exactly(
            ["http://example.com/a", 1],
            ["http://example.com/a?b=c", 2],
            ["http://example.com/a?b=c&d=e", 1],
          )
        end
      end

      context "with a google analytics tracking code and a hash" do
        before do
          @url =
            TopicLinkClick.create_from(
              url: "http://discourse.org?_ga=1.16846778.221554446.1071987018#faq",
              topic_id: @topic.id,
              ip: "127.0.0.3",
            )
          @click = TopicLinkClick.last
        end

        it "creates a click" do
          expect(@click).to be_present
          expect(@url).to eq("http://discourse.org?_ga=1.16846778.221554446.1071987018#faq")
        end
      end

      context "with a valid url and topic_id" do
        before do
          @url =
            TopicLinkClick.create_from(url: @topic_link.url, topic_id: @topic.id, ip: "127.0.0.3")
          @click = TopicLinkClick.last
        end

        it "creates a click" do
          expect(@click).to be_present
          expect(@click.topic_link).to eq(@topic_link)
          expect(@url).to eq(@topic_link.url)
        end
      end
    end
  end
end