class Api::V1::AccessPointsController < Api::V1::ApiController
  protect_from_forgery with: :null_session
  before_filter :user_authentication_for_api
  before_filter :check_network_presence?, only: [:provisioning], if: Proc.new { |c| c.request.format.json? }
  before_filter :check_bulk_update_permission, only: [:release_devices]
  # skip_load_and_authorize_resource :only => [:rout_inventory_list]
  

  api :GET, "/v1/access_points/list_inventory", "List Inventory"  
  # param :access_token, String, :desc => "User Access Token", :required => true
  param :mac_id, String, :desc => "Device Macid"

  def list_inventory
    inventory = params[:mac_id]
    routers_list = @user.organisation.router_inventories.page(params[:page] || 1).per(params[:per_page] || 100)
    router = @user.organisation.router_inventories.where(:mac_id => inventory)
    current, total, per_page = routers_list.current_page, routers_list.num_pages, routers_list.limit_value
    if inventory.present?
      if router.present?
        render json: { data: { devices: router.map{|n| n.json_build(params["ui"].present? ? params["ui"] : nil)} }, status: 200 }
      else
        render json: { msg: "Requested inventory not found check your router inventory", error_code: 1011}, status: 404
      end
    else
      render json: { data: { devices: routers_list.map{|n| n.json_build(params["ui"].present? ? params["ui"] : nil)}, meta: { current: current, previous: (current>1 ? (current-1) : nil), next: (current==total ? nil : (current+1)), per_page: per_page, pages: total, count: routers_list.total_count }}, status: 200 }
    end
  end

  def rout_inventory_list
    organisation_id = current_user.organisation_id
    per_page = params[:per_page].to_i > 0 ? params[:per_page].to_i : 10
    sort_column = (params[:sort_column].presence || "name").to_s
    sort_direction = params[:sort_direction].presence || 'asc'
    page = params[:page].to_i
    page = 1 if page < 1
    

    # Normalize search term
    search_term =
      if params[:search].is_a?(Hash)
        params[:search][:value].to_s.strip
      elsif params[:search].is_a?(String)
        params[:search].strip
      else
        ""
      end

    # Check for device status filter
    device_status_filter = nil
    if search_term == "up"
      device_status_filter = "up"
      search_term = "" 
    elsif search_term == "down"
      device_status_filter = "down"
      search_term = "" 
    end

    search_after = params[:search_after].present? ? JSON.parse(params[:search_after]) : nil

    # Try to get search_after from cache if page > 1 and search_after not passed explicitly
    if search_after.nil? && page > 1
      cached_token_key = "inventory_search:org_#{organisation_id}:page_#{page - 1}"
      search_after = Rails.cache.read(cached_token_key)
    end

    # Get permitted networks for user and check manage permission
    permitted_network_ids = nil
    can_manage_router_inventory = false
    unless current_user.is_user_allowed_all_networks?
      permitted_network_ids = current_user.get_all_permitted_networks_for_non_admin.pluck(:id)
      if permitted_network_ids.present?
        ability = Ability::InventoryAbility.new(current_user)
        can_manage_router_inventory = ability.can?(:manage, RouterInventory)
      end
    end

    @total_count = nil
    total_cache_key = "network_search_total:org_#{organisation_id}"

    # Get total count only if no search_after (for deep pagination skip count)
    if search_after.blank?
      @total_count = RouterInventory.count_search_results(
        search_term,
        organisation_id,
        permitted_network_ids,
        can_manage_router_inventory
      )
       Rails.cache.write(total_cache_key, @total_count, expires_in: 30.minutes)
    else
      @total_count = Rails.cache.read(total_cache_key) || "unknown"
    end

    # Perform search with search_after
    search_response = RouterInventory.elk_search(
      search_term,
      sort_column,
      sort_direction,
      organisation_id,
      per_page,
      permitted_network_ids,
      search_after,
      can_manage_router_inventory
    )
    @inventory_data = search_response.results

    # Extract meta info
    total_count = @total_count || "unknown"
    has_more = search_response.has_more? rescue false
    next_search_after = search_response.next_search_after rescue nil

    # Cache next_search_after for next page (page+1)
    if has_more && next_search_after.present?
      cache_key = "inventory_search:org_#{organisation_id}:page_#{page}"
      Rails.cache.write(cache_key, next_search_after, expires_in: 30.minutes)
    end

    # Generate a page token (UUID) only if there is a next page
    next_page_token = has_more ? SecureRandom.uuid : nil

    # Get active records
    inventory_ids = @inventory_data.map { |item| item._id.to_i }
    active_records_hash = {}
    RouterInventory.where(id: inventory_ids).each do |record|
      active_records_hash[record.id] = record
    end

    # Apply device status filter if present
    if device_status_filter.present?
      filtered_data = []
      @inventory_data.each do |inv|
        active_record = active_records_hash[inv._id.to_i]
        next if active_record.nil?
        
        hb_info = active_record.is_up?(with_last_hb: true)
        is_up = hb_info ? hb_info[:is_up] : false
        
        if device_status_filter == "up" && is_up
          filtered_data << inv
        elsif device_status_filter == "down" && !is_up
          filtered_data << inv
        end
      end
      @inventory_data = filtered_data
    end

    return unless request.format.json?

    # Pagination metadata
    total_pages = total_count == "unknown" ? nil : (total_count.to_f / per_page).ceil
    pagination_meta = {
      current: page,
      previous: page > 1 ? page - 1 : nil,
      next: has_more ? page + 1 : nil,
      pages: total_pages,
      total: total_count
    }

    # Shared meta block
    base_meta = {
      page_token: params[:page_token],
      next_page_token: next_page_token,
      next_search_after: has_more ? next_search_after : nil,
      has_more: has_more,
      count: @inventory_data.size
    }.merge(pagination_meta)

    # Format response
    response_data = {
      draw: params[:draw].to_i,
      recordsTotal: total_count == "unknown" ? @inventory_data.size : total_count,
      recordsFiltered: total_count == "unknown" ? @inventory_data.size : total_count,
      data: @inventory_data.map do |inv|
        active_record = active_records_hash[inv._id.to_i]
        hb_info = active_record.nil? ? nil : active_record.is_up?(with_last_hb: true)
        {
          id: inv.id,
          name: inv.name,
          mac_id: inv.mac_id,
          router_serial_number: inv.router_serial_number,
          firmware_version: inv.firmware_version,
          network_name: inv.network_name,
          tags: inv.tags,
          location_network_id: inv.location_network_id,
          organisation_id: inv.organisation_id,
          last_hb_on: hb_info && hb_info[:last_hb_on] ? hb_info[:last_hb_on].utc.strftime('%d/%m/%Y %H:%M:%S %:z') : nil,
          activated_on: inv.activated_on,
          is_up: hb_info ? hb_info[:is_up] : nil,
          hp_name: inv.hardware_part_name,
          external_guid: inv.external_guid 
        }
      end,
      meta: base_meta
    }

    render json: response_data
  end

  
  api :POST, "/v1/access_points/provisioning", "AP provisioning"  
  # param :access_token, String, :desc => "User Access Token", :required => true
  param :mac_id, String, :desc => "Device Macid", :required => true
  param :network_id, String, :desc => "Network Id", :required => true

  def provisioning
    if params[:mac_id].present?
      macids = params[:mac_id]
      invalid_mac_ids = []
      unless macids.blank?
        inventory = macids.split(',')
        inventory.each do |router|
          status, message = RouterInventory.validate_router(router,@user.organisation)
          invalid_mac_ids << router if status == false
        end
      end
      # Move to network if device is already present in inventory
      valid_ris = @user.organisation.router_inventories.where(mac_id: invalid_mac_ids, location_network_id: nil)
      invalid_mac_ids -= valid_ris.pluck(:mac_id)
      if valid_ris.present?
        ln = @user.location_networks.where(id: params[:network_id]).last
        if ln.present?
          valid_ris.each do |ri|
            ri.location_network_id = ln.id
            ri.save
          end
        end
      end
      if !invalid_mac_ids.present?
        inventory = macids.split(',')
        ln = @user.location_networks.where(id: params[:network_id]).last
        inventory.each{|router| RouterInventory.router_activation([router,ln.try(:network_name)],@user,false)}
        render json: { msg: "Successfully created new Devices", devices: inventory.map{|router| RouterInventory.where(mac_id: router).last.json_build}, status: 200 }
      elsif @user.organisation.router_inventories.count.to_i >= @user.organisation.no_of_aps.to_i
        render json: { msg: "You have reached the maximum limit #{@user.organisation.no_of_aps} to add inventories", error_code: 1005 }, status: 422 
      else
        render json: { msg: "Following macids #{invalid_mac_ids.join(",")} are invalid or already provisioned. Please check your macids", error_code: 1006 }, status: 422
      end
    else
      render json: { msg: "macids or network name is blank", error_code: 1012 }, status: 422
    end
  end

  api :PUT, "/v1/access_points/apply_configuration", "Apply Configuration"  
  # param :access_token, String, :desc => "User Access Token", :required => true
  param :mac_ids, String, :desc => "Device Macid", :required => true
  param :init_template_name, String, :desc => "Init Template Name"
  param :init_template_id, Integer, :desc => "Init Template Id"
  
  def apply_configuration
    @saved_mac_ids = []
    init_template_name = params[:init_template_name]
    init_template_id = params[:init_template_id]
    if (init_template_name.present? || init_template_id.present?) && (params[:mac_ids].present?)
      macids_params = params[:mac_ids].split(",")
      all_mac_ids = macids_params.map{|n| n.length == 12 ? n.scan(/../).join(":").upcase : n.upcase}
      if init_template_name 
        init_template = @user.organisation.init_templates.where(:name => init_template_name).first
      else
        init_template = @user.organisation.init_templates.where(:id => init_template_id).first
      end
      device_ids = @user.organisation.router_inventories.where(:mac_id => all_mac_ids)
      if init_template.present? && device_ids.present?
        routers = device_ids
        routers.each do |router|
          router.uplinks.update_all(is_deleted: true)
          uplink_details = init_template.details
          uplink_details.each do |uplink_detail|
            if uplink_detail["uplink_type"] == "1"
              router.uplinks.build(uplink_detail)
            elsif uplink_detail["uplink_type"] == "2" || uplink_detail["uplink_type"] == "3"
              router.uplinks.build(uplink_detail) if router.hardware_part.try(:wifi_uplink) == true
            elsif uplink_detail["uplink_type"] == "4"
              router.uplinks.build(uplink_detail) if router.hardware_part.try(:usb) == true
            end
          end
          @saved_mac_ids << router.mac_id if router.save
        end
        @non_saved_ids = all_mac_ids - @saved_mac_ids
        @result = status_message(@non_saved_ids.count,'add_init_template')
      elsif device_ids.blank?
        render json: { msg: "Requested macids are not found. Please check your macids", error_code: 1013 }, status: 404
      else
        render json: { msg: "Requested configuration is not found. Please check your configuration name", error_code: 1014 }, status: 404
      end
    else
      render json: { msg: "Init Template name or macid is not present", error_code: 1015 }, status: 422
    end
  end

  def current_status
    ri = @user.organisation.router_inventories.where(:mac_id => params[:mac_id]).last
    last_hb_data = $redis.hgetall "AP:#{params[:mac_id]}"
    last_seen_time = (last_hb_data["last_hb"].blank? ? nil : last_hb_data["last_hb"].to_i)
    if ri.blank?
      render json: {status: 404, msg: "Requested device is not found. Please check the device MAC.", error_code: 1011}, status: 404
    else
      switch_conf = ri.get_associated_switch_configuration
      if ri.switch? && switch_conf.present?
        last_mc_data = MonitoringChild.where("info.NASID" => params[:mac_id]).last
        switch_port_conf = switch_conf.switch_port_configurations
        port_status = []
        (switch_port_conf || []).each do |port|
          (last_mc_data.try(:sw_prt) || []).each do |mc_data|
            if mc_data["NO"].to_i+1 == port.port_number
              port_status << {:name => port.name, :status => mc_data["up"] ? "UP" : "DOWN", :port_no => port.port_number, :speed => mc_data["SPEED"], :up_time => mc_data["UPTIME"], :duplex => mc_data["DUPLEX"], :total_usage => {rx: mc_data["RX_BYTES"], tx: mc_data["TX_BYTES"]}, :rate => {tx: mc_data["TX_RATE"], rx: mc_data["RX_RATE"]}, :multicast => mc_data["MULTICAST"], :power => mc_data["POWER"]}
            end
          end
        end
      end
      res_data = {:status => 200, :data => {:name => last_hb_data['AP'], :device_status => (ri.is_up? ? "UP" : "DOWN"), :firmware_version => last_hb_data['version'], :network_name => last_hb_data['Network'], :hardware_model => last_hb_data['HW'], :public_ip => last_hb_data['pub_ip'], :last_seen_time => last_seen_time}}
      if ri.switch?
        render :json => res_data.merge({:port_status => port_status}), status: 200
      else
        render :json => res_data, status: 200
      end
    end
  end

  def status_message count,type
    if count.to_i == 0 && type == 'add_init_template'
      render json: { msg: "Successfully added uplink config to selected devices", status: 200 }
    else
      render json: { msg: "Oops! For the following macids #{@non_saved_ids.join(",")} of devices uplink config are not applied", status: 1016 }, status: 422
    end
  end

  def release_devices
    unless params[:devices].blank?
      released_mac_ids = []
      not_released_mac_ids = []
      routers = @user.organisation.router_inventories.where(:mac_id => params[:devices])
      routers.each do |router|
        router.location_network_id = nil
        if router.save
          released_mac_ids << router.mac_id
        else
          not_released_mac_ids << router.mac_id
        end
      end
      render json: {msg: "Devices not found. Please check the devices."}, status: 422 if routers.blank?
      render json: {msg: "Successfully Released devices from network", success_devices: released_mac_ids, failed_devices: not_released_mac_ids} if released_mac_ids.present? && not_released_mac_ids.blank?
      render json: {msg: "Successfully Released few devices from network. Please check the failed devices for non released devices.", success_devices: released_mac_ids, failed_devices: not_released_mac_ids}, status: 422 if released_mac_ids.present? && not_released_mac_ids.present?
      render json: {msg: "Oops! There was an issue in releasing devices from network. Please check the failed devices.", error_code: 1017, success_devices: released_mac_ids, failed_devices: not_released_mac_ids}, status: 422 if released_mac_ids.blank? && not_released_mac_ids.present?
    else
      render json: { msg: "Please provide atleast one device.", error_code: 1026}, status: 422
    end
  end

  def remove_devices
    unless params[:devices].blank?
      removed_mac = []
      not_removed_mac = []
      routers = @user.organisation.router_inventories.where(:mac_id => params[:devices])
      routers.each do |router|
        if router.destroy
          removed_mac << router.mac_id
        else
          not_removed_mac << router.mac_id
        end
      end
      render json: {msg: "Devices not found. Please check the devices."}, status: 422 if routers.blank?
      render json: {msg: "Successfully removed devices from Inventory", success_devices: removed_mac, failed_devices: not_removed_mac} if removed_mac.present? && not_removed_mac.blank?
      render json: {msg: "Successfully removed few devices from Inventory. Please check the failed devices for non removed devices.", success_devices: removed_mac, failed_devices: not_removed_mac}, status: 422 if removed_mac.present? && not_removed_mac.present?
      render json: {msg: "Oops! There was an issue in removing devices from Inventory. Please check the failed devices.", error_code: 1018, success_devices: removed_mac, failed_devices: not_removed_mac}, status: 422 if removed_mac.blank? && not_removed_mac.present?
    else
      render json: { msg: "Please provide atleast one device.", error_code: 1026}, status: 422
    end
  end

  def move_devices
    unless params[:devices].blank?
      network = @user.organisation.location_networks.find_by_id(params[:existing_network_id])
      if network.blank?
        render json: {msg: "Network is not found. Please check your existing network id", error_code: 1019}, status: 404
      else
        routers = @user.organisation.router_inventories.where(:id => params[:devices]).pluck(:id)
        routers = @user.organisation.router_inventories.where(:mac_id => params[:devices]).pluck(:id) unless routers.present?
        response = @user.organisation.router_inventories.change_network(routers,network,"no")
        if response.blank?
          render json: { msg: "Successfully Moved Devices", devices: routers.map{|router| RouterInventory.find_by_id(router).json_build}}
        else
          failed_devices = @user.organisation.router_inventories.where(:id => response.flatten).pluck(:mac_id)
          success_devices = @user.organisation.router_inventories.where(:id => (routers - response.flatten)).map &:json_build
          render json: { msg: "Oops! there was some problem in Moving Devices", error_code: 1020, devices: success_devices, failed_devices: failed_devices }, status: 422
        end
      end
    else
      render json: { msg: "Please provide atleast one device.", error_code: 1026}, status: 422
    end
  end

  def get_devices_from_serial
    serials = params[:serial].split(',').map(&:strip)
    @devices = MasterRouterInventory.where(serial: serials)
    render json: {data: {devices: @devices.map(&:json_build)}}
  end

  def check_bulk_update_permission
    unless !(current_user.is_tier_2?)
      if params[:devices].present?
        requested_ids = params[:devices]

        # Count invalid router ids
        invalid_count = current_user.router_inventories.where(mac_id: requested_ids).where.not(location_network_id: [nil, ""]).count
        if invalid_count > 0
          if request.format.json?
            render json: {msg: "You are not authorized to remove devices assigned to a network"}, status: 403
          else
            flash[:alert] = "You are not authorized to perform this action"
            redirect_to request.referer || root_path
          end
        end
      end
    end
  end
end