require "rails_helper"

# rubocop:disable Layout/LineLength

RSpec.configure do |config|
  # Specify a root folder where Swagger JSON files are generated
  # NOTE: If you"re using the rswag-api to serve API descriptions, you"ll need
  # to ensure that it"s configured to serve Swagger from the same folder
  config.swagger_root = Rails.root.join("swagger").to_s

  # Define one or more Swagger documents and provide global metadata for each one
  # When you run the "rswag:specs:swaggerize" rake task, the complete Swagger will
  # be generated at the provided relative path under swagger_root
  # By default, the operations defined in spec files are added to the first
  # document below. You can override this behavior by adding a swagger_doc tag to the
  # the root example_group in your specs, e.g. describe "...", swagger_doc: "v2/swagger.json"
  config.swagger_docs = {
    "v1/api_v1.json" => {
      openapi: "3.0.3",
      info: {
        title: "Forem API V1",
        version: "1.0.0",
        description: "Access Forem articles, users and other resources via API.
        For a real-world example of Forem in action, check out [DEV](https://www.dev.to).
        All endpoints can be accessed with the 'api-key' header and a accept header, but
        some of them are accessible publicly without authentication.

        Dates and date times, unless otherwise specified, must be in
        the [RFC 3339](https://tools.ietf.org/html/rfc3339) format."
      },
      paths: {},
      servers: [
        {
          url: "https://dev.to/api",
          description: "Production server"
        },
      ],
      security: [{ "api-key": [] }],
      components: {
        securitySchemes: {
          "api-key": {
            type: :apiKey,
            name: "api-key",
            in: :header,
            description: "API Key authentication.

Authentication for some endpoints, like write operations on the
Articles API require a DEV API key.

All authenticated endpoints are CORS disabled, the API key is intended for non-browser scripts.

### Getting an API key

To obtain one, please follow these steps:

  - visit https://dev.to/settings/extensions
  - in the \"DEV API Keys\" section create a new key by adding a
    description and clicking on \"Generate API Key\"

    ![obtain a DEV API Key](https://user-images.githubusercontent.com/37842/172718105-bd93664e-76e0-477d-99c4-265dda0b06c5.png)

  - You'll see the newly generated key in the same view
    ![generated DEV API Key](https://user-images.githubusercontent.com/37842/172718151-e7fe26a0-9937-42e8-96c6-333acdab9e49.png)"
          }
        },
        parameters: {
          pageParam: {
            in: :query,
            name: :page,
            required: false,
            description: "Pagination page",
            schema: {
              type: :integer,
              format: :int32,
              minimum: 1,
              default: 1
            }
          },
          perPageParam10to1000: {
            in: :query,
            name: :per_page,
            required: false,
            description: "Page size (the number of items to return per page). \
The default maximum value can be overridden by \"API_PER_PAGE_MAX\" environment variable.",
            schema: {
              type: :integer,
              format: :int32,
              minimum: 1,
              maximum: 1000,
              default: 10
            }
          },
          perPageParam24to1000: {
            in: :query,
            name: :per_page,
            required: false,
            description: "Page size (the number of items to return per page). \
The default maximum value can be overridden by \"API_PER_PAGE_MAX\" environment variable.",
            schema: {
              type: :integer,
              format: :int32,
              minimum: 1,
              maximum: 1000,
              default: 24
            }
          },
          perPageParam30to1000: {
            in: :query,
            name: :per_page,
            required: false,
            description: "Page size (the number of items to return per page). \
The default maximum value can be overridden by \"API_PER_PAGE_MAX\" environment variable.",
            schema: {
              type: :integer,
              format: :int32,
              minimum: 1,
              maximum: 1000,
              default: 30
            }
          },
          perPageParam30to100: {
            in: :query,
            name: :per_page,
            required: false,
            description: "Page size (the number of items to return per page). \
The default maximum value can be overridden by \"API_PER_PAGE_MAX\" environment variable.",
            schema: {
              type: :integer,
              format: :int32,
              minimum: 1,
              maximum: 100,
              default: 30
            }
          },
          perPageParam80to1000: {
            in: :query,
            name: :per_page,
            required: false,
            description: "Page size (the number of items to return per page). \
The default maximum value can be overridden by \"API_PER_PAGE_MAX\" environment variable.",
            schema: {
              type: :integer,
              format: :int32,
              minimum: 1,
              maximum: 1000,
              default: 80
            }
          },
          listingCategoryParam: {
            name: :category,
            in: :query,
            description: "Using this parameter will return listings belonging to the
              requested category.",
            schema: {
              type: :string
            },
            example: "cfp"
          }
        },
        schemas: {
          ArticleFlareTag: {
            description: "Flare tag of the article",
            type: :object,
            properties: {
              name: { type: :string },
              bg_color_hex: { description: "Background color (hexadecimal)", type: :string, nullable: true },
              text_color_hex: { description: "Text color (hexadecimal)", type: :string, nullable: true }
            }
          },
          ArticleIndex: {
            description: "Representation of an article or post returned in a list",
            type: :object,
            properties: {
              type_of: { type: :string },
              id: { type: :integer, format: :int32 },
              title: { type: :string },
              description: { type: :string },
              cover_image: { type: :string, format: :url, nullable: true },
              readable_publish_date: { type: :string },
              social_image: { type: :string, format: :url },
              tag_list: { type: :array, items: {
                type: :string
              } },
              tags: { type: :string },
              slug: { type: :string },
              path: { type: :string, format: "path" },
              url: { type: :string, format: :url },
              canonical_url: { type: :string, format: :url },
              positive_reactions_count: { type: :integer, format: :int32 },
              public_reactions_count: { type: :integer, format: :int32 },
              created_at: { type: :string, format: "date-time" },
              edited_at: { type: :string, format: "date-time", nullable: true },
              crossposted_at: { type: :string, format: "date-time", nullable: true },
              published_at: { type: :string, format: "date-time" },
              last_comment_at: { type: :string, format: "date-time" },
              published_timestamp: { description: "Crossposting or published date time", type: :string,
                                     format: "date-time" },
              reading_time_minutes: { description: "Reading time, in minutes", type: :integer, format: :int32 },
              user: { "$ref": "#/components/schemas/SharedUser" },
              flare_tag: { "$ref": "#/components/schemas/ArticleFlareTag" },
              organization: { "$ref": "#/components/schemas/SharedOrganization" }
            },
            required: %w[type_of id title description cover_image readable_publish_date
                         social_image tag_list tags slug path url canonical_url comments_count
                         positive_reactions_count public_reactions_count created_at edited_at
                         crossposted_at published_at last_comment_at published_timestamp user
                         reading_time_minutes]
          },
          VideoArticle: {
            description: "Representation of an Article with video",
            type: :object,
            properties: {
              type_of: { type: :string },
              id: { type: :integer, format: :int64 },
              path: { type: :string },
              cloudinary_video_url: { type: :string },
              title: { type: :string },
              user_id: { type: :integer, format: :int64 },
              video_duration_in_minutes: { type: :string },
              video_source_url: { type: :string },
              user: {
                description: "Author of the article",
                type: :object,
                properties: {
                  name: { type: :string }
                }
              }
            }
          },
          Article: {
            description: "Representation of an Article to be created/updated",
            type: :object,
            properties: {
              article: {
                type: :object,
                properties: {
                  title: { type: :string },
                  body_markdown: { type: :string },
                  published: { type: :boolean, default: false },
                  series: { type: :string, nullable: true },
                  main_image: { type: :string, nullable: true },
                  canonical_url: { type: :string, nullable: true },
                  description: { type: :string },
                  tags: { type: :string },
                  organization_id: { type: :integer, nullable: true }
                }
              }
            }
          },
          Organization: {
            description: "Representation of an Organization",
            type: :object,
            properties: {
              type_of: { type: :string },
              username: { type: :string },
              name: { type: :string },
              summary: { type: :string },
              twitter_username: { type: :string },
              github_username: { type: :string },
              url: { type: :string },
              location: { type: :string },
              joined_at: { type: :string },
              tech_stack: { type: :string },
              tag_line: { type: :string, nullable: true },
              story: { type: :string, nullable: true }
            }
          },
          FollowedTag: {
            description: "Representation of a followed tag",
            type: :object,
            properties: {
              id: { description: "Tag id", type: :integer, format: :int64 },
              name: { type: :string },
              points: { type: :number, format: :float }
            },
            required: %w[id name points]
          },
          Tag: {
            description: "Representation of a tag",
            type: :object,
            properties: {
              id: { description: "Tag id", type: :integer, format: :int64 },
              name: { type: :string },
              bg_color_hex: { type: :string, nullable: true },
              text_color_hex: { type: :string, nullable: true }
            }
          },
          Page: {
            description: "Representation of a page object",
            type: :object,
            properties: {
              title: { type: :string, description: "Title of the page" },
              slug: { type: :string, description: "Used to link to this page in URLs, must be unique and URL-safe" },
              description: { type: :string, description: "For internal use, helps similar pages from one another" },
              body_markdown: { type: :string, description: "The text (in markdown) of the ad (required)",
                               nullable: true },
              body_json: { type: :string, description: "For JSON pages, the JSON body", nullable: true },
              is_top_level_path: { type: :boolean,
                                   description: "If true, the page is available at '/{slug}' instead of '/page/{slug}', use with caution" },
              social_image: { type: :object, nullable: true },
              template: {
                type: :string, enum: Page::TEMPLATE_OPTIONS, default: "contained",
                description: "Controls what kind of layout the page is rendered in"
              }
            },
            required: %w[title slug description template]
          },
          PodcastEpisodeIndex: {
            description: "Representation of a podcast episode returned in a list",
            type: :object,
            properties: {
              type_of: { type: :string },
              id: { type: :integer, format: :int32 },
              class_name: { type: :string },
              path: { type: :string, format: "path" },
              title: { type: :string },
              image_url: { description: "Podcast episode image url or podcast image url", type: :string, format: :url },
              podcast: { "$ref": "#/components/schemas/SharedPodcast" }
            },
            required: %w[type_of class_name id path title image_url podcast]
          },
          ProfileImage: {
            description: "A profile image object",
            type: :object,
            properties: {
              type_of: { description: "Return profile_image", type: :string },
              image_of: { description: "Determines the type of the profile image owner (user or organization)",
                          type: :string },
              profile_image: { description: "Profile image (640x640)", type: :string },
              profile_image_90: { description: "Profile image (90x90)", type: :string }
            }
          },
          SharedUser: {
            description: "The resource creator",
            type: :object,
            properties: {
              name: { type: :string },
              username: { type: :string },
              twitter_username: { type: :string, nullable: true },
              github_username: { type: :string, nullable: true },
              website_url: { type: :string, format: :url, nullable: true },
              profile_image: { description: "Profile image (640x640)", type: :string },
              profile_image_90: { description: "Profile image (90x90)", type: :string }
            }
          },
          SharedOrganization: {
            description: "The organization the resource belongs to",
            type: :object,
            properties: {
              name: { type: :string },
              username: { type: :string },
              slug: { type: :string },
              profile_image: { description: "Profile image (640x640)", type: :string, format: :url },
              profile_image_90: { description: "Profile image (90x90)", type: :string, format: :url }
            }
          },
          User: {
            description: "The representation of a user returned in a list",
            type: :object,
            properties: {
              type_of: { type: :string },
              id: { type: :integer, format: :int64 },
              username: { type: :string },
              name: { type: :string },
              summary: { type: :string, nullable: true },
              twitter_username: { type: :string },
              github_username: { type: :string },
              website_url: { type: :string, nullable: true },
              location: { type: :string, nullable: true },
              joined_at: { type: :string },
              profile_image: { type: :string }
            }
          },
          ExtendedUser: {
            description: "The representation of a user",
            type: :object,
            properties: {
              type_of: { type: :string },
              id: { type: :integer, format: :int64 },
              username: { type: :string },
              name: { type: :string },
              summary: { type: :string, nullable: true },
              twitter_username: { type: :string },
              github_username: { type: :string },
              email: { type: :string, nullable: true, description: "Email (if user allows displaying email on their profile) or nil" },
              website_url: { type: :string, nullable: true },
              location: { type: :string, nullable: true },
              joined_at: { type: :string },
              profile_image: { type: :string },
              badge_ids: { type: :array,
                           items: { type: :integer },
                           description: "ids of the badges awarded to the user" }
            }
          },
          MyUser: {
            description: "The representation of a user when accessed by themselves",
            type: :object,
            properties: {
              type_of: { type: :string },
              id: { type: :integer, format: :int64 },
              username: { type: :string },
              name: { type: :string },
              summary: { type: :string, nullable: true },
              twitter_username: { type: :string },
              github_username: { type: :string },
              email: { type: :string, nullable: true, description: "Email (if user allows displaying email on their profile) or nil" },
              website_url: { type: :string, nullable: true },
              location: { type: :string, nullable: true },
              joined_at: { type: :string },
              profile_image: { type: :string },
              badge_ids: { type: :array,
                           items: { type: :integer },
                           description: "ids of the badges awarded to the user" },
              followers_count: { type: :integer }
            }
          },
          SharedPodcast: {
            description: "The podcast that the resource belongs to",
            type: :object,
            properties: {
              title: { type: :string },
              slug: { type: :string },
              image_url: { description: "Podcast image url", type: :string, format: :url }
            }
          },
          Comment: {
            description: "A Comment on an Article or Podcast Episode",
            type: :object,
            properties: {
              type_of: { type: :string },
              id_code: { type: :string },
              created_at: { type: :string, format: "date-time" },
              image_url: { description: "Podcast image url", type: :string, format: :url }
            }
          },
          UserInviteParam: {
            description: "User invite parameters",
            type: :object,
            properties: {
              email: { type: :string },
              name: { type: :string, nullable: true }
            }
          },
          Billboard: {
            description: "Billboard, aka Widget, ex. Display Ad",
            type: :object,
            properties: {
              id: { type: :integer, description: "The ID of the Billboard" },
              name: { type: :string, description: "For internal use, helps distinguish ads from one another" },
              body_markdown: { type: :string, description: "The text (in markdown) of the ad (required)" },
                        approved: { type: :boolean, description: "Ad must be both published and approved to be in rotation" },
          published: { type: :boolean, description: "Ad must be both published and approved to be in rotation" },
          expires_at: { type: :string, format: :"date-time", nullable: true,
                        description: "Timestamp when the billboard expires. After this time, the billboard will automatically be marked as not approved." },
              organization_id: { type: :integer, description: "Identifies the organization to which the ad belongs", nullable: true },
              creator_id: { type: :integer, description: "Identifies the user who created the ad.", nullable: true },
              placement_area: { type: :string, enum: Billboard::ALLOWED_PLACEMENT_AREAS,
                                description: "Identifies which area of site layout the ad can appear in" },
              tag_list: { type: :string, description: "Tags on which this ad can be displayed (blank is all/any tags)" },
              exclude_article_ids: { type: :string,
                                     nullable: true,
                                     description: "Articles this ad should *not* appear on (blank means no articles are disallowed, and this ad can appear next to any/all articles). Comma-separated list of integer Article IDs" }, # rubocop:disable Layout/LineLength
              audience_segment_id: { type: :integer,
                                     description: "Specifies a specific audience segment who will see this billboard" },
              audience_segment_type: { type: :string,
                                       enum: AudienceSegment.type_ofs.keys,
                                       description: "Specifies a group of users who will see this billboard (must match audience_segment_id if both provided)" },
              target_geolocations: { type: :array,
                                     items: { type: :string },
                                     description: "Locations to show this billboard in (blank means it will be shown in all locations). Specified as a comma-separated list or array of ISO 3166-2 country and optionally region codes)" },
              display_to: { type: :string, enum: Billboard.display_tos.keys, default: "all",
                            description: "Potentially limits visitors to whom the ad is visible" },
              type_of: { type: :string, enum: Billboard.type_ofs.keys, default: "in_house",
                         description: <<~DESCRIBE
                           Types of the billboards:
                           in_house (created by admins),
                           community (created by an entity, appears on entity's content),
                           external ( created by an entity, or a non-entity, can appear everywhere)
                         DESCRIBE
                }
            },
            required: %w[name body_markdown placement_area]
          },
          Segment: {
            description: "A manually managed audience segment",
            type: "object",
            properties: {
              id: { type: :integer, description: "The ID of the segment" },
              type_of: { type: :string, enum: ["manual"], default: "manual", description: "Marks the segment as manually managed (other types are internal)" },
              user_count: { type: :integer, description: "The current number of users in the segment" }
            }
          },
          SegmentUserIds: {
            type: "object",
            properties: {
              user_ids: {
                type: :array,
                items: { type: :integer },
                maxItems: 10_000
              }
            }
          }
        }
      }
    }
  }

  # Specify the format of the output Swagger file when running "rswag:specs:swaggerize".
  # The swagger_docs configuration option has the filename including format in
  # the key, this may want to be changed to avoid putting yaml in json files.
  # Defaults to json. Accepts ":json" and ":yaml".
  config.swagger_format = :json
end

# Convenience method for creating an example section for a response section
module Rswag
  module Specs
    module ExampleGroupHelpers
      def add_examples
        after do |example|
          # No metadata to generate for empty responses like 201 and 204.
          next unless respond_to?(:response) && response&.body.present?

          # Generate the examples for the API docs.
          example.metadata[:response][:content] = {
            "application/json" => {
              example: JSON.parse(response.body, symbolize_names: true)
            }
          }
        end
      end
    end
  end
end

# rubocop:enable Layout/LineLength
