defmodule Soap.Xsd do
  @moduledoc """
  Provides functions for parsing xsd file.
  """

  import SweetXml, except: [parse: 1]

  alias Soap.{Request, Type}

  @spec parse(String.t(), keyword()) :: {:ok, map()} | {:error, atom()}
  def parse(path, opts \\ []) do
    if URI.parse(path).scheme do
      parse_from_url(path, opts)
    else
      parse_from_file(path)
    end
  end

  @spec parse_from_file(String.t()) :: {:ok, map()} | {:error, atom()}
  def parse_from_file(path) do
    case File.read(path) do
      {:ok, xsd} -> parse_xsd(xsd)
      error_response -> error_response
    end
  end

  @spec parse_from_url(String.t(), keyword()) :: {:ok, map()} | {:error, atom()}
  def parse_from_url(path, opts \\ []) do
    request_opts = Keyword.merge([follow_redirect: true, max_redirect: 5], opts)

    case Request.get_http_client().get(path, [], request_opts) do
      {:ok, %HTTPoison.Response{status_code: 404}} -> {:error, :not_found}
      {:ok, %HTTPoison.Response{body: body}} -> parse_xsd(body)
      {:error, %HTTPoison.Error{reason: reason}} -> {:error, reason}
    end
  end

  @spec parse_xsd(String.t()) :: {:ok, map()}
  defp parse_xsd(xsd) do
    parsed_response = %{
      simple_types: get_simple_types(xsd),
      complex_types: Type.get_complex_types(xsd, "//xsd:schema/xsd:complexType")
    }

    {:ok, parsed_response}
  catch
    :exit, err ->
      {:error, err}
  end

  @spec get_simple_types(String.t()) :: list()
  defp get_simple_types(wsdl) do
    wsdl
    |> xpath(
      ~x"//xsd:schema/xsd:simpleType"l,
      name: ~x"./@name"s,
      restriction: [
        ~x"./xsd:restriction"o,
        base: ~x"./@base"s,
        min_inclusive: ~x"./xsd:minInclusive/@value"io,
        max_inclusive: ~x"./xsd:maxInclusive/@value"io,
        max_length: ~x"./xsd:maxLength/@value"io,
        total_digits: ~x"./xsd:totalDigits/@value"io,
        fraction_digits: ~x"./xsd:fractionDigits/@value"io,
        pattern: ~x"./xsd:pattern/@value"so,
        enumeration: ~x"./xsd:enumeration/@value"lso
      ],
      list: [
        ~x"./xsd:list"o,
        item_type: ~x"./@itemType"s
      ],
      union: [
        ~x"./xsd:union"o,
        types: ~x"./xsd:simpleType/xsd:restriction/@base"lo
      ]
    )
  end
end
