defmodule WhoNeedHelp.ContentRemoval.Notice do
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id

  schema "content_removal_notices" do
    field :reference, :string

    field :regime, Ecto.Enum,
      values: [:general, :dsa, :take_it_down],
      default: :general

    field :category, Ecto.Enum,
      values: [
        :illegal_content,
        :privacy_violation,
        :copyright,
        :impersonation,
        :non_consensual_intimate_media,
        :child_sexual_abuse_material,
        :threat_to_life_or_safety,
        :other
      ]

    field :status, Ecto.Enum,
      values: [
        :open,
        :urgent_review,
        :reviewing,
        :needs_information,
        :actioned,
        :rejected,
        :closed
      ],
      default: :open

    field :submitter_name, :string
    field :contact_email, :string

    field :relationship, Ecto.Enum, values: [:self, :authorized_representative, :other]

    field :content_locations, :string
    field :explanation, :string
    field :legal_basis, :string
    field :contact_verified_at, :utc_datetime
    field :good_faith, :boolean, default: false
    field :accurate_complete, :boolean, default: false
    field :electronic_signature, :string
    field :resolution_note, :string
    field :reviewed_at, :utc_datetime
    field :response_due_at, :utc_datetime
    field :acknowledgement_sent_at, :utc_datetime
    field :decision_sent_at, :utc_datetime
    belongs_to :requester, WhoNeedHelp.Accounts.User
    belongs_to :reviewed_by, WhoNeedHelp.Accounts.User
    timestamps(type: :utc_datetime)
  end

  def submission_changeset(notice, attrs) do
    notice
    |> cast(attrs, [
      :category,
      :submitter_name,
      :contact_email,
      :relationship,
      :content_locations,
      :explanation,
      :legal_basis,
      :good_faith,
      :accurate_complete,
      :electronic_signature
    ])
    |> validate_required([
      :category,
      :content_locations,
      :explanation,
      :reference,
      :regime
    ])
    |> update_change(:contact_email, &normalize_email/1)
    |> validate_format(:contact_email, ~r/^[^@,;\s]+@[^@,;\s]+$/,
      message: "must have the @ sign and no spaces"
    )
    |> validate_length(:submitter_name, min: 2, max: 160)
    |> validate_length(:contact_email, max: 160)
    |> validate_length(:content_locations, min: 5, max: 10_000)
    |> validate_length(:explanation, min: 20, max: 10_000)
    |> validate_length(:legal_basis, max: 2_000)
    |> validate_length(:electronic_signature, min: 2, max: 160)
    |> validate_acceptance(:good_faith,
      message: "you must confirm your good-faith belief"
    )
    |> validate_acceptance(:accurate_complete,
      message: "you must confirm that the notice is accurate and complete"
    )
    |> validate_identity_requirements()
    |> validate_content_locations()
    |> validate_take_it_down_category()
    |> unique_constraint(:reference)
  end

  def moderation_changeset(notice, attrs) do
    notice
    |> cast(attrs, [:status, :resolution_note, :reviewed_at, :reviewed_by_id, :decision_sent_at])
    |> validate_required([:status, :reviewed_at, :reviewed_by_id])
    |> validate_length(:resolution_note, max: 10_000)
  end

  def content_urls(%__MODULE__{content_locations: locations}) when is_binary(locations) do
    locations
    |> String.split(~r/[\r\n]+/, trim: true)
    |> Enum.map(&String.trim/1)
  end

  def content_urls(_notice), do: []

  defp validate_content_locations(changeset) do
    validate_change(changeset, :content_locations, fn :content_locations, locations ->
      urls = locations |> String.split(~r/[\r\n]+/, trim: true) |> Enum.map(&String.trim/1)

      cond do
        urls == [] ->
          [content_locations: "must contain at least one exact URL"]

        length(urls) > 20 ->
          [content_locations: "cannot contain more than 20 URLs"]

        Enum.any?(urls, &(not valid_http_url?(&1))) ->
          [content_locations: "must contain one full HTTP or HTTPS URL per line"]

        true ->
          []
      end
    end)
  end

  defp validate_take_it_down_category(changeset) do
    if get_field(changeset, :regime) == :take_it_down and
         get_field(changeset, :category) not in [
           :non_consensual_intimate_media,
           :child_sexual_abuse_material
         ] do
      add_error(changeset, :category, "must describe intimate visual material")
    else
      changeset
    end
  end

  defp validate_identity_requirements(changeset) do
    if get_field(changeset, :category) == :child_sexual_abuse_material and
         get_field(changeset, :regime) != :take_it_down do
      changeset
    else
      validate_required(changeset, [
        :submitter_name,
        :contact_email,
        :relationship,
        :electronic_signature
      ])
    end
  end

  defp valid_http_url?(value) do
    case URI.parse(value) do
      %URI{scheme: scheme, host: host}
      when scheme in ["http", "https"] and is_binary(host) and host != "" ->
        true

      _ ->
        false
    end
  end

  defp normalize_email(email), do: email |> String.trim() |> String.downcase()
end
