module App::Controllers
  class LinkController < App::Lib::BaseController
    include App::Models
    include App::Lib
    include App::Services

    def initialize(@env : HTTP::Server::Context)
      super(@env)
    end

    def create
      body = parse_body(["url"])
      url = body["url"].to_s

      query = Database::Query.where(url: url, user_id: current_user_id).limit(1)
      existing_link = Database.all(Link, query, preload: [:clicks]).first?
      if existing_link
        return render_json({"data" => App::Serializers::Link.new(existing_link)})
      end

      link = Link.new
      link.url = url
      link.user_id = current_user_id
      link.slug = SlugService.shorten_url(url, current_user_id)

      changeset = Database.insert(link)
      if !changeset.valid?
        raise App::UnprocessableEntityException.new(@env, map_changeset_errors(changeset.errors))
      end

      inserted_link = Database.get!(Link, changeset.instance.id)

      render_json({"data" => App::Serializers::Link.new(inserted_link)}, 201)
    end

    def list_all
      limit, cursor = pagination_params

      query = Database::Query.where(user_id: current_user_id)
      query = query.where("id < ?", cursor) if cursor
      query = query.order_by("id DESC").limit(limit + 1)

      links = Database.all(Link, query)

      paginated_response(links, limit) { |link| App::Serializers::Link.new(link) }
    end

    def get
      link_id = @env.params.url["id"].to_i64

      query = Database::Query.where(id: link_id, user_id: current_user_id).limit(1)
      link = Database.all(Link, query).first?
      raise App::NotFoundException.new(@env) if link.nil?

      clicks_query = Database::Query.where(link_id: link_id)
                                   .order_by("id DESC")
                                   .limit(100)
      link.clicks = Database.all(Click, clicks_query)

      render_json({"data" => App::Serializers::Link.new(link)})
    end

    def list_clicks
      link_id = @env.params.url["id"].to_i64

      # Verify link exists and belongs to user
      link_query = Database::Query.where(id: link_id, user_id: current_user_id).limit(1)
      link = Database.all(Link, link_query).first?
      raise App::NotFoundException.new(@env) if link.nil?

      limit, cursor = pagination_params

      query = Database::Query.where(link_id: link_id)
      query = query.where("id < ?", cursor) if cursor
      query = query.order_by("id DESC").limit(limit + 1)

      clicks = Database.all(Click, query)

      paginated_response(clicks, limit) { |click| App::Serializers::Click.new(click) }
    end

    def update
      id = @env.params.url["id"].to_i64
      body = parse_body(["url"])
      new_url = body["url"].to_s

      query = Database::Query.where(id: id).limit(1)
      link = Database.all(Link, query, preload: [:clicks]).first?

      raise App::NotFoundException.new(@env) if link.nil?
      raise App::ForbiddenException.new(@env) if link.user_id != current_user_id

      # Check for existing URL
      existing_query = Database::Query.where(url: new_url, user_id: current_user_id).limit(1)
      if Database.all(Link, existing_query).first?
        raise App::UnprocessableEntityException.new(@env, { "url" => ["URL already exists"] })
      end

      link.url = new_url
      link.slug = SlugService.shorten_url(new_url, current_user_id)

      changeset = Database.update(link)
      if !changeset.valid?
        raise App::UnprocessableEntityException.new(@env, map_changeset_errors(changeset.errors))
      end

      render_json({"data" => App::Serializers::Link.new(link)})
    end

    def delete
      id = @env.params.url["id"].to_i64

      link = Database.get(Link, id)
      raise App::NotFoundException.new(@env) if !link
      raise App::ForbiddenException.new(@env) if link.user_id != current_user_id

      result = Database.raw_exec("DELETE FROM links WHERE id = (?)", link.id)
      if result.rows_affected == 0
        raise App::UnprocessableEntityException.new(@env, { "id" => ["Row delete failed"] })
      end

      @env.response.status_code = 204
    end

    private def current_user : User
      @env.get("user").as(User)
    end

    private def current_user_id : Int64
      current_user.id.as(Int64)
    end

    private def pagination_params
      limit = (@env.params.query["limit"]? || "100").to_i32
      cursor = @env.params.query["cursor"]?
      {limit, cursor}
    end

    private def paginated_response(items, limit)
      has_more = items.size > limit
      items = items[0...limit] if has_more
      next_cursor = has_more ? items.last.id : nil

      render_json({
        "data" => items.map { |item| yield item },
        "pagination" => {
          "has_more" => has_more,
          "next_cursor" => next_cursor
        }
      })
    end
  end
end
