defmodule Blogex.LinkValidatorTest do
  use ExUnit.Case
  alias Blogex.LinkValidator

  describe "extract_links/1" do
    test "extracts internal blog links from markdown body" do
      body =
        "Check out [hello world](/blog/engineering/hello-world) and [release v1](/blog/releases/v1-0-0)."

      assert LinkValidator.extract_links(body) == [
               "/blog/engineering/hello-world",
               "/blog/releases/v1-0-0"
             ]
    end

    test "ignores external links" do
      body = "See [GitHub](https://github.com) and [internal](/blog/engineering/post)."

      assert LinkValidator.extract_links(body) == ["/blog/engineering/post"]
    end

    test "ignores non-blog internal links" do
      body = "See [/about](/about) and [/blog/engineering/post](/blog/engineering/post)."

      assert LinkValidator.extract_links(body) == ["/blog/engineering/post"]
    end

    test "returns empty list when no internal blog links" do
      body = "Just external links: [GitHub](https://github.com)."

      assert LinkValidator.extract_links(body) == []
    end

    test "handles multiple links on one line" do
      body = "[a](/blog/engineering/a) [b](/blog/releases/b) [c](/blog/engineering/c)"

      assert LinkValidator.extract_links(body) == [
               "/blog/engineering/a",
               "/blog/releases/b",
               "/blog/engineering/c"
             ]
    end

    test "handles links with query strings" do
      body = "[link](/blog/engineering/post?foo=bar)"

      assert LinkValidator.extract_links(body) == ["/blog/engineering/post?foo=bar"]
    end

    test "handles links with anchor fragments" do
      body = "[link](/blog/engineering/post#section)"

      assert LinkValidator.extract_links(body) == ["/blog/engineering/post#section"]
    end

    test "handles empty body" do
      assert LinkValidator.extract_links("") == []
    end
  end

  describe "validate_link/1" do
    test "validates correct engineering link" do
      assert LinkValidator.validate_link("/blog/engineering/my-post") == :ok
    end

    test "validates correct releases link" do
      assert LinkValidator.validate_link("/blog/releases/v1-0-0") == :ok
    end

    test "rejects unknown blog ID" do
      assert LinkValidator.validate_link("/blog/unknown/post") ==
               {:error, "unknown blog ID: unknown"}
    end

    test "rejects uppercase blog ID" do
      assert LinkValidator.validate_link("/blog/Engineering/post") ==
               {:error, "unknown blog ID: Engineering"}
    end

    test "rejects empty slug" do
      assert LinkValidator.validate_link("/blog/engineering/") ==
               {:error, "empty slug"}
    end

    test "rejects slug with uppercase letters" do
      assert LinkValidator.validate_link("/blog/engineering/My-Post") ==
               {:error, "slug must be lowercase alphanumeric with hyphens: My-Post"}
    end

    test "rejects slug with special characters" do
      assert LinkValidator.validate_link("/blog/engineering/hello@world") ==
               {:error, "slug must be lowercase alphanumeric with hyphens: hello@world"}
    end

    test "rejects slug with spaces" do
      assert LinkValidator.validate_link("/blog/engineering/hello world") ==
               {:error, "slug must be lowercase alphanumeric with hyphens: hello world"}
    end

    test "allows single-word slug" do
      assert LinkValidator.validate_link("/blog/engineering/hello") == :ok
    end

    test "allows hyphenated slug" do
      assert LinkValidator.validate_link("/blog/engineering/my-cool-post") == :ok
    end

    test "allows slug with numbers" do
      assert LinkValidator.validate_link("/blog/releases/v1-2-3") == :ok
    end

    test "rejects slug starting with hyphen" do
      assert LinkValidator.validate_link("/blog/engineering/-post") ==
               {:error, "slug must be lowercase alphanumeric with hyphens: -post"}
    end

    test "rejects slug ending with hyphen" do
      assert LinkValidator.validate_link("/blog/engineering/post-") ==
               {:error, "slug must be lowercase alphanumeric with hyphens: post-"}
    end

    test "rejects consecutive hyphens" do
      assert LinkValidator.validate_link("/blog/engineering/post--name") ==
               {:error, "slug must be lowercase alphanumeric with hyphens: post--name"}
    end

    test "returns :ok for link with query string and valid slug" do
      assert LinkValidator.validate_link("/blog/engineering/post?foo=bar") == :ok
    end

    test "returns :ok for link with anchor fragment and valid slug" do
      assert LinkValidator.validate_link("/blog/engineering/post#section") == :ok
    end

    test "rejects non-blog path" do
      assert LinkValidator.validate_link("/about") ==
               {:error, "not a blog link: /about"}
    end

    test "rejects malformed link" do
      assert LinkValidator.validate_link("not-a-url") ==
               {:error, "not a blog link: not-a-url"}
    end
  end

  describe "validate_links/1" do
    test "returns :ok when all links are valid" do
      links = [
        "/blog/engineering/hello-world",
        "/blog/releases/v1-0-0"
      ]

      assert LinkValidator.validate_links(links) == :ok
    end

    test "returns errors for invalid links" do
      links = [
        "/blog/engineering/hello-world",
        "/blog/unknown/post",
        "/blog/releases/My-Post"
      ]

      assert LinkValidator.validate_links(links) == {
               :error,
               [
                 {2, "/blog/unknown/post", "unknown blog ID: unknown"},
                 {3, "/blog/releases/My-Post",
                  "slug must be lowercase alphanumeric with hyphens: My-Post"}
               ]
             }
    end

    test "returns :ok for empty list" do
      assert LinkValidator.validate_links([]) == :ok
    end

    test "reports line numbers correctly" do
      links = [
        "/blog/engineering/ok",
        "/blog/bad/slug",
        "/blog/releases/ok"
      ]

      assert LinkValidator.validate_links(links) == {
               :error,
               [{2, "/blog/bad/slug", "unknown blog ID: bad"}]
             }
    end
  end

  describe "validate_body/2" do
    test "returns :ok when body has no internal blog links" do
      body = "Just text, no links."

      assert LinkValidator.validate_body(body, :engineering) == :ok
    end

    test "returns :ok when all links are valid" do
      body = "[link](/blog/engineering/post)"

      assert LinkValidator.validate_body(body, :engineering) == :ok
    end

    test "returns errors with post context" do
      body = "[link](/blog/unknown/post)"

      assert LinkValidator.validate_body(body, :engineering) == {
               :error,
               [
                 {
                   1,
                   "/blog/unknown/post",
                   "unknown blog ID: unknown",
                   post_id: nil
                 }
               ]
             }
    end

    test "includes post_id in error tuples when provided" do
      body = "[link](/blog/unknown/post)"

      assert LinkValidator.validate_body(body, :engineering, post_id: "test-post") == {
               :error,
               [
                 {
                   1,
                   "/blog/unknown/post",
                   "unknown blog ID: unknown",
                   post_id: "test-post"
                 }
               ]
             }
    end
  end
end
