local FORMNAME = "fediauth-check"
local FORMNAMEFEDI = "fediauth-check-fedi"

-- time for fediauth code verification
local fediauth_time = 300

-- playername => start_time
local fediauth_sessions = {}

local formspecfediadd = "size[9,10]" ..
            		"label[1,7;Input your fediverse account handle]" ..
			"image[1.5,0.6;7,7;fediverse.png]" ..
            		"field[1,9;4,1;fediverse_account_url;@nick@example.com;]" ..
			"field_close_on_enter[fediverse_account_url;false]" ..
            		"button[5,8.7;3,1;submit;Send code]"

local feditempstore = {}
local failed_counter = {}

local cubic_jail_entities = {}
function fediauth.remove_lock_cube(playername)
	if cubic_jail_entities[playername] == nil then return end
	for _, obj in ipairs(cubic_jail_entities[playername]) do
		obj:remove()
	end
	cubic_jail_entities[playername] = nil
	minetest.log("action", "[fediauth] cubic jail removed for: '" .. playername .. "'")
end

minetest.register_entity("fediauth:checkmark", {
	initial_properties = {
		pointable = false,
		armor_groups = { immortal = 1 },
		visual = "sprite",
		visual_size = {x = 0.5, y = 0.5},
		textures = { "checkmark.png^[opacity:180" },
		use_texture_alpha = true,
		static_save = false,
		glow = 5,
	},
	on_detach = function(self, parent) self.object:remove() end
})

minetest.register_entity("fediauth:stopper", {
	initial_properties = {
		pointable = false,
		physical = true,
		collide_with_objects = true,
		armor_group = { immortal = 1 },
		collisionbox = { -0.5, -0.5, -0.5, 0.5, 0.5, 0.5 },
		visual = "sprite",
		visual_size = {x = 1, y = 1 },
		textures = { "ignore" },
		use_texture_alpha = true,
		static_save = true,
		glow = 10,
	}
})

function fediauth.verified_checkmark(player, verified)
    local tag = player:get_player_name()
    local props = player:get_properties()
    if verified then
	local obj = minetest.add_entity({x=0,y=2,z=0}, "fediauth:checkmark", nil)
	obj:set_attach(player, "Head", {x = 0, y = 12, z = 0})
	player:set_properties({nametag = props.nametag .. " [FEDI]", nametag_color = "#00ff00" })
    end
end

-- Code formspec on join for fediauth enabled players
minetest.register_on_joinplayer(function(player)
    local playername = player:get_player_name()
    local player_pos = player:getpos()

    if fediauth.is_player_bypassed(playername) then return end
    if fediauth.is_player_enabled(playername) or minetest.settings:get_bool("fediauth.fedi_required", false) then
        minetest.log("action", "[fediauth] session start for player: '" .. playername .. "'")
	if minetest.settings:get_bool("fediauth.create_lock_jail_cube") then
		local cube = {}
		for x=-2,2 do
			for y=-2,2 do
				for z=-2,2 do
					if x ~= 0 then 
						table.insert(cube, {player_pos.x + x, player_pos.y + y, player_pos.z + z})
					elseif y ~= 0 and y ~= 1 then
						table.insert(cube, {player_pos.x + x, player_pos.y + y, player_pos.z + z})
					elseif z ~= 0 then
						table.insert(cube, {player_pos.x + x, player_pos.y + y, player_pos.z + z})
					end
				end
			end
		end
		local cube_entities = {}
		for _, i in ipairs(cube) do
			table.insert(cube_entities, minetest.add_entity({x=i[1], y=i[2], z=i[3]}, "fediauth:stopper", nil))
		end
		cubic_jail_entities[playername] = cube_entities
		cube = nil
        	minetest.log("action", "[fediauth] cubic jail created for: '" .. playername .. "'")
	end

        -- start fediauth session time
        fediauth_sessions[player:get_player_name()] = os.time()

        -- revoke important privs and re-grant again on code-verification
        fediauth.revoke_privs(playername)

	-- save password for prevent changes
	fediauth.save_passw(playername)

	-- if fedi only allowed
	if minetest.settings:get_bool("fediauth.fedi_required", false) then
		local existsfedi = fediauth.storage:get_string(playername .. "_fedi")
		if existsfedi == "" or not existsfedi then
        		minetest.log("action", "[fediauth] request fedi account for player: '" .. playername .. "'")
        		minetest.show_formspec(playername, FORMNAMEFEDI, formspecfediadd)
			return
		end
	end

	local secret_b32 = fediauth.get_player_secret_b32(playername)
	local codeseq = fediauth.give_code(secret_b32)
	local fedihandle = fediauth.storage:get_string(playername .. "_fedi"):split("@")
	if fediauth.is_home_instance(fedihandle[2]) then
		fediauth.send_code(codeseq[1], "@" .. fedihandle[1], playername)
	else
		fediauth.send_code(codeseq[1], "@" .. fedihandle[1] .. "@" .. fedihandle[2], playername)
	end
        -- send verification formspec
        local formspec = "size[10,2]" ..
            "label[1,0;Please check your fedi account and enter code]" ..
            "field[1,1.3;4,1;code;Code;]" ..
            "button_exit[5,1;3,1;submit;Verify]"

        minetest.show_formspec(playername, FORMNAME, formspec)
    end
end)

-- prevent flooding codes
minetest.register_on_prejoinplayer(function(name, ip)
    if (failed_counter[name] or 0) >= 2 then
	return "Please try later, your attempts has expired"
    end
end)

local function attempts_cleanup(name)
    for k, v in pairs(failed_counter) do
	if v >= 2 then
		failed_counter[k] = nil
	end
    end
    minetest.after(120, attempts_cleanup)
end
minetest.after(120, attempts_cleanup)

-- clear fediauth session on leave
minetest.register_on_leaveplayer(function(player, timed_out)
    local playername = player:get_player_name()
    fediauth_sessions[playername] = nil
    fediauth.discard_passw(playername)
    fediauth.remove_lock_cube(playername)
end)

-- check sessions periodically and kick if timed out
local function session_check()
    local now = os.time()
    for name, start_time in pairs(fediauth_sessions) do
        if (now - start_time) > fediauth_time then
            minetest.kick_player(name, "fediauth code validation timed out")
            fediauth_sessions[name] = nil
        end
    end
    minetest.after(5, session_check)
end
minetest.after(5, session_check)

-- fediauth check
minetest.register_on_player_receive_fields(function(player, formname, fields)
    if formname ~= FORMNAME and formname ~= FORMNAMEFEDI then
        return
    end

    local playername = player:get_player_name()
    local secret_b32 = fediauth.get_player_secret_b32(playername)

    -- check for new player or doesn't have fedi account
    if fields.fediverse_account_url then
	-- basic prevent mention spam and limit length
	if not string.starts(fields.fediverse_account_url, "@") or string.len(fields.fediverse_account_url) < 3 or string.len(fields.fediverse_account_url) > 100 then
		minetest.chat_send_player(playername, minetest.colorize("#ff0000", "Try again, your input is incorrect"))
		minetest.show_formspec(playername, FORMNAMEFEDI, formspecfediadd)
		return
	end
	local fedihandle = fields.fediverse_account_url:split("@")
	if #fedihandle ~= 2 then
		minetest.chat_send_player(playername, minetest.colorize("#ff0000", "Incorrect format"))
		minetest.show_formspec(playername, FORMNAMEFEDI, formspecfediadd)
		return
	end
	if fediauth.check_for_restricted_instance(fedihandle[2]) then
		minetest.chat_send_player(playername, minetest.colorize("#ff0000", fedihandle[2] .. " has restricted, try another..."))
		minetest.show_formspec(playername, FORMNAMEFEDI, formspecfediadd)
		return
	end
        local secret_b32 = fediauth.get_player_secret_b32(playername)
        local codeseq = fediauth.give_code(secret_b32)
	if fediauth.is_home_instance(fedihandle[2]) then
		fediauth.send_code(codeseq[1], "@" .. fedihandle[1], playername)
	else
		fediauth.send_code(codeseq[1], "@" .. fedihandle[1] .. "@" .. fedihandle[2], playername)
	end
	feditempstore[playername] = fields.fediverse_account_url
	local formspec = "size[9,10]" ..
            "label[1,7;Check code on " .. minetest.formspec_escape(fields.fediverse_account_url) .. "]" ..
            "field[1,9;4,1;code;Code;]" ..
            "button_exit[5,8.7;3,1;submit;Verify]"

        minetest.show_formspec(playername, FORMNAME, formspec)
	return
    end




    if fediauth.check_code(secret_b32, fields.code) then
	local fedi_account = fediauth.storage:get_string(playername .. "_fedi")

	-- if player without fediverse (for prevent write account handle if code incorrect)
	if fedi_account == "" and feditempstore[playername] then
		fediauth.storage:set_string(playername .. "_fedi", feditempstore[playername])
		fedi_account = feditempstore[playername]
		feditempstore[playername] = nil
	end

        minetest.chat_send_player(playername, minetest.colorize("#00ff00", "fediauth code validation succeeded for " .. fedi_account))
        fediauth_sessions[playername] = nil
        fediauth.regrant_privs(playername)
	fediauth.discard_passw(playername)
	fediauth.verified_checkmark(player, true)
	if minetest.settings:get_bool("fediauth.create_lock_jail_cube") then
		fediauth.remove_lock_cube(playername)
	end
    else
	fediauth.discard_passw(playername)
        minetest.kick_player(playername, "fediauth code validation failed")
        fediauth.regrant_privs(playername)
	if minetest.settings:get_bool("fediauth.create_lock_jail_cube") then
		fediauth.remove_lock_cube(playername)
	end
	failed_counter[playername] = (failed_counter[playername] or 0) + 1
    end
end)