--
-- Copyright (C) 2013-2015 Sipwise GmbH <development@sipwise.com>
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
module:depends("disco");
local softreq = require "util.dependencies".softreq;
local ut_jid = require "util.jid";
local sql = require "util.sql";
local st = require "util.stanza";
local template = require "util.template";
local prosodyctl = require "util.prosodyctl"
local dataforms_new = require "util.dataforms".new;
local ut = require "ngcp.utils";
local hosts = prosody.hosts;
local rex = softreq "rex_pcre";
if not rex then
  rex = require "rex_pcre2";
end

local form_layout = dataforms_new{
  title= 'User Directory Search';
  instructions = 'Please provide the following information to search for subscribers';
  {
    type  = 'text-single',
    label = 'e164 Phone number',
    name  = 'e164',
    required = false,
  },
  {
    type  = 'text-single',
    label = 'domain',
    name  = 'domain',
    required = false,
  },
};

local form_item = template[[
      <item>
        <field var='{name}'>
          <value>{value}</value>
        </field>
      </item>
]];

local form_reply = {
	domain = template[[
  <query xmlns='jabber:iq:search'>
    <x xmlns='jabber:x:data' type='result'>
      <field type='hidden' var='FORM_TYPE'>
        <value>jabber:iq:search</value>
      </field>
      <reported>
        <field type='text-single'
             label='domain'
             var='domain'/>
      </reported>
    </x>
  </query>
]],
	e164 = template[[
  <query xmlns='jabber:iq:search'>
    <x xmlns='jabber:x:data' type='result'>
      <field type='hidden' var='FORM_TYPE'>
        <value>jabber:iq:search</value>
      </field>
      <reported>
        <field type='text-single'
             label='e164 Phone number'
             var='e164'/>
      </reported>
    </x>
  </query>
]],
  form = template[[
  <query xmlns='jabber:iq:search'>
    <instructions>Use the enclosed form to search</instructions>
    <x xmlns='jabber:x:data' type='form'>
      <title>User Directory Search</title>
      <instructions>Please provide the following information to search for subscribers</instructions>
      <field type='hidden' var='FORM_TYPE'>
        <value>jabber:iq:search</value>
      </field>
      <field type='text-single'
             label='e164 Phone number'
             var='e164'/>
      <field type='text-single'
             label='domain'
             var='domain'/>
    </x>
    <nick/>
  </query>
]]
};

local usr_replacements_query = [[
SELECT vrr.match_pattern, vrr.replace_pattern FROM provisioning.voip_preferences vp
  LEFT JOIN provisioning.voip_usr_preferences vup ON vup.attribute_id = vp.id
  LEFT JOIN provisioning.voip_subscribers vs ON vs.id = vup.subscriber_id
  LEFT JOIN provisioning.voip_domains vd ON vd.id = vs.domain_id
  LEFT JOIN provisioning.voip_rewrite_rule_sets vrrs ON vrrs.callee_in_dpid = vup.value
  LEFT JOIN provisioning.voip_rewrite_rules vrr ON vrr.set_id = vrrs.id
   AND vrr.direction = 'in'
   AND vrr.field = 'callee'
WHERE vp.attribute = 'rewrite_callee_in_dpid' AND vs.username = ? AND vd.domain = ?
  ORDER BY vrr.priority ASC;
]];

local dom_replacements_query = [[
SELECT vrr.match_pattern, vrr.replace_pattern FROM provisioning.voip_preferences vp
  LEFT JOIN provisioning.voip_dom_preferences vdp ON vdp.attribute_id = vp.id
  LEFT JOIN provisioning.voip_domains vd ON vd.id = vdp.domain_id
  LEFT JOIN provisioning.voip_rewrite_rule_sets vrrs ON vrrs.callee_in_dpid = vdp.value
  LEFT JOIN provisioning.voip_rewrite_rules vrr ON vrr.set_id = vrrs.id
   AND vrr.direction = 'in' AND vrr.field = 'callee'
WHERE vp.attribute = 'rewrite_callee_in_dpid'  AND vd.domain = ?
  ORDER BY vrr.priority ASC;
]];

local locale_query = [[
SELECT vp.attribute, vup.value FROM provisioning.voip_preferences vp
  LEFT JOIN provisioning.voip_usr_preferences vup ON vup.attribute_id = vp.id
  LEFT JOIN provisioning.voip_subscribers vs ON vs.id = vup.subscriber_id
  LEFT JOIN provisioning.voip_domains vd ON vd.id = vs.domain_id
WHERE (vp.attribute = 'ac' or vp.attribute = 'cc')
  AND vs.username = ?
  AND vd.domain = ?;
]];

local lookup_query = [[
SELECT username,domain FROM kamailio.dbaliases
WHERE alias_username=?;
]];

local default_params = module:get_option("sql");
local engine;

-- Reconnect to DB if necessary
local function reconect_check()
	if not engine.conn:ping() then
		engine.conn = nil;
		engine:connect();
	end
end

local function normalize_number(user, host, number)
	local locale_info = {};
	reconect_check();
	for row in engine:select(locale_query, user, host) do
		locale_info["caller_"..row[1]] = row[2];
	end

	local replacement_regexes = {};
	local usr_pref = 0;
	for row in engine:select(usr_replacements_query, user, host) do
		usr_pref = 1;
		module:log("debug", "user rewrite_callee_in_dpid preference found");
		local patt, repl = row[1], row[2]
			:gsub("%$avp%(s:([a-zA-Z_]+)%)", locale_info)
			:gsub("\\", "%%"):gsub("%%%%", "\\");
		table.insert(replacement_regexes, { patt, repl });
	end
	if usr_pref == 0 then
		for row in engine:select(dom_replacements_query, host) do
			module:log("debug", "domain rewrite_callee_in_dpid preference found");
			local patt, repl = row[1], row[2]
				:gsub("%$avp%(s:([a-zA-Z_]+)%)", locale_info)
				:gsub("\\", "%%"):gsub("%%%%", "\\");
			table.insert(replacement_regexes, { patt, repl });
		end
	end

	for _, rule in ipairs(replacement_regexes) do
		local new_number, n_matches = rex.gsub(number, rule[1], rule[2]);
		if n_matches > 0 then
			module:log("debug", "rule [%s] matched [%s]->[%s]",
				tostring(rule[1]), tostring(number), tostring(new_number));
			number = new_number;
			break;
		end
	end
	return number;
end

local function search_by_number(number)
	local results = {};
	reconect_check();
	module:log("debug", "search jids with number:[%s]", tostring(number));
	for result in engine:select(lookup_query, number) do
		table.insert(results, result[1].."@"..result[2]);
	end
	return results;
end

local function get_dataform_items(vals, fieldname)
	local res = {};

	for _,v in ipairs(vals) do
		table.insert(res, form_item.apply({ name = fieldname, value = v }));
	end
	return res;
end

local function search_domains(dom)
	local hosts_keys = ut.table.keys(hosts);
	local res = {};

	module:log("debug", "search for domain: %s", tostring(dom));
	if dom == '*' then
		res = get_dataform_items(hosts_keys, 'domain');
	elseif ut.table.contains(hosts_keys, dom) then
		res = get_dataform_items({dom,}, 'domain');
	end
	return res;
end

function module.command(arg)
	local warn = prosodyctl.show_warning;
	local command = arg[1];
	if not command then
		warn("Valid subcommands: normalize");
		return 0;
	end
	table.remove(arg, 1);
	if command == "normalize" then
		if #arg ~= 2 then
			warn("Usage: normalize USER@HOST NUMBER");
			return 1;
		end
		local user_jid, number = arg[1], arg[2];
		local user, host = ut_jid.prepped_split(user_jid);
		if not (user and host) then
			warn("Invalid JID: "..user_jid);
			return 1;
		end
		print(normalize_number(user, host, number));
	elseif command == "query" then
		if #arg ~= 1 then
			warn("Usage: query NUMBER");
			warn("  NUMBER must be normalized (see 'normalize' command)");
			return 1;
		end
		local results = search_by_number(arg[1]);
		for _, jid in ipairs(results) do
			print("", jid);
		end
	end
	return 0;
end

local function append_x_items(form, values, reply)
	local x = form:get_child('x', 'jabber:x:data');
	for _, v in ipairs(values) do
		x:add_child(v):up();
	end
	reply:add_child(x);
end

if module:get_host_type() ~= "component" then
	error("Don't load mod_sipwise_vjud manually,"..
		" it should be for a component", 0);
end

module:add_feature("jabber:iq:search");
module:add_feature("jabber:x:data"); -- dataforms
module:hook("iq/host/jabber:iq:search:query", function(event)
	local origin, stanza = event.origin, event.stanza;
	module:log("debug", "stanza[%s]", tostring(stanza));
	if stanza.attr.type == "get" then
		return origin.send(st.reply(stanza):add_child(form_reply.form.apply({})));
	else

		local user, host = ut_jid.split(stanza.attr.from);
		local query = stanza:get_child("query",'jabber:iq:search');
		local form_stanza = query:get_child("x",'jabber:x:data');
		local reply;
		local search_number = stanza.tags[1]:get_child_text("nick");
		local search_domain

		if form_stanza then
			local form_data, form_errors = form_layout:data(form_stanza);
			if form_errors and form_errors['e164'] then
				reply = st.error_reply(stanza, "modify",
					"bad-request", "e164: "..form_errors['e164']);
				return origin.send(reply);
			else
				search_number = form_data['e164'] or search_number;
				search_domain = form_data['domain'];
			end
		end

		if search_number then
			local number = normalize_number(user, host, search_number);
			local data = search_by_number(number);
			reply = st.reply(stanza):query("jabber:iq:search");
			if form_stanza then
				append_x_items(form_reply.e164.apply({}),
					get_dataform_items(data, 'e164'), reply);
			else
				for _, jid in ipairs(data) do
					reply:tag("item", { jid = jid }):up();
				end
			end
		elseif search_domain then
			reply = st.reply(stanza):query("jabber:iq:search");
			append_x_items(form_reply.domain.apply({}),
				search_domains(search_domain), reply);
		else
			reply = st.error_reply(stanza, "modify",
					"bad-request", "no domain, nick or e164 field found");
		end

		return origin.send(reply);
	end
end);

local function normalize_params(params)
	assert(params.driver and params.database,
		"Configuration error: Both the SQL driver and the database need to be specified");
	return params;
end

function module.load()
	if prosody.prosodyctl then return; end
	local engines = module:shared("/*/sql/connections");
	local params = normalize_params(module:get_option("auth_sql", default_params));
	engine = engines[sql.db2uri(params)];
	if not engine then
		module:log("debug", "Creating new engine");
		engine = sql:create_engine(params);
		engines[sql.db2uri(params)] = engine;
	end
	engine:connect();
	module:log("debug", "load OK");
end