# typed: strict
class Scraper::SofurryHttpClientConfig < Scraper::HttpClientConfig
  API_HOSTS = %w[api.sofurry.com api2.sofurry.com]

  class SofurryCookies < T::Struct
    const :phpsessid, String
    const :sfuser, String
    const :yii_csrf_token, String
  end

  sig { override.returns(T.nilable(T::Array[T.untyped])) }
  def cookies
    phpsessid = GlobalState.get("sofurry-phpsessid")
    sfuser = GlobalState.get("sofurry-sfuser")
    yii_csrf_token = GlobalState.get("sofurry-yii_csrf_token")
    raise "sofurry phpsessid is not set" if phpsessid.blank?
    raise "sofurry sfuser is not set" if sfuser.blank?
    raise "sofurry yii_csrf_token is not set" if yii_csrf_token.blank?

    [
      {
        domain: ".sofurry.com",
        cookies: [
          { name: "PHPSESSID", value: phpsessid, path: "/" },
          { name: "sfuser", value: sfuser, path: "/" },
          { name: "YII_CSRF_TOKEN", value: yii_csrf_token, path: "/" },
        ],
      },
    ]
  end

  sig { override.params(performer: Scraper::CurlHttpPerformer).void }
  def do_login(performer)
    phpsessionid =
      GlobalState.find_or_create_by(key: "sofurry-phpsessid") do |gs|
        gs.value_type = :string
      end

    sfuser =
      GlobalState.find_or_create_by(key: "sofurry-sfuser") do |gs|
        gs.value_type = :string
      end

    yii_csrf_token =
      GlobalState.find_or_create_by(key: "sofurry-yii_csrf_token") do |gs|
        gs.value_type = :string
      end

    if phpsessionid.value.present? && sfuser.value.present? &&
         yii_csrf_token.value.present?
      logger.info(format_tags("sofurry already logged in"))
      return
    end

    phpsessionid.with_lock do
      if phpsessionid.value.blank?
        cookies = do_sofurry_login(performer)
        phpsessionid.value = cookies.phpsessid
        sfuser.value = cookies.sfuser
        yii_csrf_token.value = cookies.yii_csrf_token
        logger.info(format_tags(make_tag("phpsessionid", cookies.phpsessid)))
        logger.info(format_tags(make_tag("sfuser", cookies.sfuser)))
        logger.info(
          format_tags(make_tag("yii_csrf_token", cookies.yii_csrf_token)),
        )
        phpsessionid.save!
        sfuser.save!
        yii_csrf_token.save!
      end
    end
  end

  sig { override.returns(T::Array[[String, Numeric]]) }
  def ratelimit
    [["sofurry.com", 0.5], ["*.sofurry.com", 0.5], ["*.sofurryfiles.com", 0.25]]
  end

  sig { override.returns(T::Array[String]) }
  def allowed_domains
    %w[sofurry.com *.sofurry.com *.sofurryfiles.com]
  end

  sig { override.returns(Integer) }
  def redirect_limit
    3
  end

  sig { override.params(request: Request).returns(Request) }
  def map_request(request)
    if API_HOSTS.include?(request.uri.host)
      request =
        request.with(
          request_headers:
            request.request_headers.merge("Accept" => "application/json"),
        )
    end

    request
  end

  sig { override.params(response: Response).returns(Response) }
  def map_response(response)
    if API_HOSTS.include?(response.uri.host) && response.response_code == 200
      response =
        response.with(
          response_headers:
            response.response_headers.merge(
              "Content-Type" => "application/json",
            ),
        )
    end

    response
  end

  private

  sig { params(performer: Scraper::CurlHttpPerformer).returns(SofurryCookies) }
  def do_sofurry_login(performer)
    username = GlobalState.get("sofurry-username")
    password = GlobalState.get("sofurry-password")
    raise "sofurry username is not set" if username.blank?
    raise "sofurry password is not set" if password.blank?
    uri = Addressable::URI.parse("https://www.sofurry.com/user/login")
    cookies = HTTP::CookieJar.new

    # First get a PHPSESSID
    request =
      Request.new(
        http_method: Scraper::CurlHttpPerformer::Method::Get,
        uri: uri,
        request_headers: {
        },
      )

    response = performer.do_request(request)
    raise "sofurry login initial GET failed" if response.response_code != 200
    process_cookies(response, cookies)
    phpsessid = get_cookie(cookies, "PHPSESSID")
    if phpsessid.blank?
      raise "sofurry phpsessid not set in initial GET response"
    end

    request =
      Request.new(
        http_method: Scraper::CurlHttpPerformer::Method::Post,
        uri: Addressable::URI.parse("https://www.sofurry.com/user/login"),
        follow_redirects: false,
        request_headers: {
          "Content-Type" => "application/x-www-form-urlencoded",
          "Cookie" =>
            HTTP::Cookie.cookie_value(cookies.cookies(uri.normalize.to_s)),
        },
        body:
          URI.encode_www_form(
            {
              "YII_CSRF_TOKEN" => "",
              "returnUrl" => "/welcome",
              "LoginForm[sfLoginUsername]" => username,
              "LoginForm[sfLoginPassword]" => password,
              "yt1" => "Login",
            },
          ),
      )
    response = performer.do_request(request)
    raise "sofurry login POST failed" if response.response_code != 302
    process_cookies(response, cookies)
    sfuser = get_cookie(cookies, "sfuser")
    raise "sofurry sfuser not set in POST response" if sfuser.blank?

    request =
      Request.new(
        http_method: Scraper::CurlHttpPerformer::Method::Get,
        uri: Addressable::URI.parse("https://www.sofurry.com/welcome"),
        request_headers: {
          "Cookie" =>
            HTTP::Cookie.cookie_value(cookies.cookies(uri.normalize.to_s)),
        },
      )
    response = performer.do_request(request)
    raise "sofurry login follow-up GET failed" if response.response_code != 200
    process_cookies(response, cookies)
    yii_csrf_token = get_cookie(cookies, "YII_CSRF_TOKEN")
    if yii_csrf_token.blank?
      raise "sofurry yii_csrf_token not set in GET response"
    end

    SofurryCookies.new(phpsessid:, sfuser:, yii_csrf_token:)
  end

  sig { params(response: Response, cookies: HTTP::CookieJar).void }
  def process_cookies(response, cookies)
    set_cookie =
      response.response_headers.find { |k, _| k.downcase == "set-cookie" }&.last
    cookies.parse(set_cookie, response.uri.to_s) if set_cookie
  end

  sig do
    params(cookies: HTTP::CookieJar, name: String).returns(T.nilable(String))
  end
  def get_cookie(cookies, name)
    T.cast(cookies.find { |c| c.name == name }&.value, T.nilable(String))
  end
end
