/**
 * dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
 * Copyright (C) 2020-2024 Aleksey Andreev (liuch)
 *
 * Available at:
 * https://github.com/liuch/dmarc-srg
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of  MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */

class Admin {
	constructor(id) {
		this._state = null;
		this._element = null;
		this._sources = null;
		this._database = null;
	}

	display() {
		let cn = document.getElementById("main-block");
		if (!this._element) {
			this._element = document.createElement("div");
			this._element.setAttribute("class", "panel-container round-border");
			this._element.appendChild(this._get_database_elements());
			this._element.appendChild(this._get_sources_elements());
		}
		cn.appendChild(this._element);
	}

	update() {
		this._get_admin_state();
	}

	title() {
		return "Admin Panel";
	}

	_get_database_elements() {
		let fr = document.createDocumentFragment();
		let h = document.createElement("h4");
		h.appendChild(document.createTextNode("Database"));
		fr.appendChild(h);
		if (!this._database) {
			this._database = new DatabaseListBox(this._create_db_item_menu_element());
		}
		fr.appendChild(this._database.element());
		return fr;
	}

	_get_sources_elements() {
		let fr = document.createDocumentFragment();
		let h = document.createElement("h4");
		h.appendChild(document.createTextNode("Report sources"));
		fr.appendChild(h);
		if (!this._sources) {
			this._sources = new SourceListBox();
		}
		fr.appendChild(this._sources.element());
		return fr;
	}

	_get_admin_state() {
		[ this._database, this._sources ].forEach(function(c) {
			c.set_status("wait");
		});

		let t = this;
		window.fetch("admin.php", {
			method: "GET",
			cache: "no-store",
			headers: HTTP_HEADERS,
			credentials: "same-origin"
		}).then(function(resp) {
			if (!resp.ok)
				throw new Error("Failed to fetch the admin data");
			return resp.json();
		}).then(function(data) {
			Common.checkResult(data);
			t._state = data;
			t._fill_data();
		}).catch(function(err) {
			Common.displayError(err);
			t._fill_data(err.message);
		});
	}

	_send_command(cmd) {
		let t = this;
		return window.fetch("admin.php", {
			method: "POST",
			cache: "no-store",
			headers: Object.assign(HTTP_HEADERS, HTTP_HEADERS_POST),
			credentials: "same-origin",
			body: JSON.stringify(cmd)
		}).then(function(resp) {
			if (!resp.ok)
				throw new Error(`Failed to send command (${resp.status})`);
			return resp.json();
		}).finally(function() {
			t._get_admin_state();
			Status.instance().update().catch(function(){});
		});
	}

	_fill_data(err_msg) {
		if (!err_msg) {
			let d = this._state.database || [];
			this._database.set_data({
				root: {
					name:     d.name || "-",
					type:     d.type || "-",
					correct:  d.correct,
					message:  d.message || "-",
					location: d.location || "-"
				},
				groups: [
					{ name: "Tables", items: this._state.database.tables || [] }
				]
			});
			this._sources.set_data({
				groups: [
					{ name: "Mailboxes", type: "mailbox", items: this._state.mailboxes || [] },
					{ name: "Directories", type: "directory", items: this._state.directories || [] },
					{ name: "Remote filesystems", type: "remotefs", items: this._state.remotefs || [] }
				]
			});
		}
		else {
			this._database.set_status("error", err_msg);
			this._sources.set_status("error", err_msg);
		}
		if (this._state && this._state.database && this._state.database.needs_upgrade) {
			document.querySelector(".db-menu-button li[data-action=upgradedb]").classList.remove("hidden");
		}
	}

	_create_db_item_menu_element() {
		const el = document.createElement("div");
		el.classList.add("button");
		const span = el.appendChild(document.createElement("span"));
		span.setAttribute("role", "button");
		span.textContent = "...";
		//
		const mn = el.appendChild(document.createElement("div"));
		mn.classList.add("db-item-menu", "popup-menu", "round-border", "hidden");
		const ul = mn.appendChild(document.createElement("ul"));
		Admin.db_actions.forEach(it => {
			const li = ul.appendChild(document.createElement("li"));
			li.title = it.long_title;
			li.dataset.title = it.title;
			li.dataset.action = it.action;
			li.appendChild(document.createElement("span")).textContent = it.name;
			if (it.action === "upgradedb") li.classList.add("hidden");
		});
		el.addEventListener("click", event => {
			event.stopPropagation();
			el.querySelector(".popup-menu").classList.toggle("hidden");
			const item = event.target.closest("li");
			if (item) this._do_db_action_password(item.dataset.action, item.dataset.title);
		});
		return el;
	}

	_do_db_action_password(action, title) {
		let ld = new LoginDialog({
			nofetch: true,
			nousername: true
		});
		document.getElementById("main-block").appendChild(ld.element());
		let that = this;
		ld.show().then(function(d) {
			if (d) {
				that._do_db_action(action, title, { password: d.password });
			}
		}).catch(function(err) {
			Common.displayError(err);
		}).finally(function() {
			ld.remove();
		});
	}

	_do_db_action(action, title, data) {
		let d = { cmd: action };
		if (data) {
			d = Object.assign(d, data);
		}
		this._send_command(d).then(function(data) {
			Common.checkResult(data);
			Notification.add({ text: title + ": " + (data.message || "Completed successfully!"), type: "info", name: "db-act" });
		}).catch(function(err) {
			Common.displayError(err);
			Notification.add({ text: title + ": " + err.message, type: "error", delay: 10000, name: "db-act" });
		});
	}
}

Admin.db_actions = [
	{
		name:       "Initiate",
		action:     "initdb",
		title:      "Initiate DB",
		long_title: "Create all needed tables and indexes in the database"
	},
	{
		name:       "Drop",
		action:     "cleandb",
		title:      "Drop tables",
		long_title: "Drop all the tables from the database"
	},
	{
		name:       "Upgrade",
		action:     "upgradedb",
		title:      "Upgrade DB",
		long_title: "Update the structure of the database"
	}
];

class DropdownListBox {
	constructor() {
		this._item_groups  = [];
		this._element      = null;
		this._root_item    = null
		this._list_element = null;
	}

	element() {
		if (!this._element) {
			let el = document.createElement("div");
			el.setAttribute("class", "round-border");
			let that = this;
			el.addEventListener("click", function(event) {
				if (event.target.closest(".root-list-block")) {
					if (that._item_groups.length > 0) {
						that._list_element.classList.toggle("hidden");
						that._root_item.element().classList.toggle("bottom-border");
					}
				}
			});
			this._element = el;
			this._update_element();
		}
		return this._element;
	}

	set_status(type, message) {
		if (type === "wait") {
			set_wait_status(this.element());
		}
		else if (type === "error") {
			set_error_status(this.element(), message);
		}
	}

	set_data(data) {
		this._root_item = new ListBoxItem();
		this._make_group_list(data);
		this._make_root_columns(data);
		if (this._element) {
			this._update_element();
		}
	}

	_update_element() {
		if (this._element.children.length != 2)
			this._element.replaceChildren(this._content_container(), this._list_container());
	}

	_content_container() {
		if (!this._root_item) {
			this._root_item = new ListBoxItem();
		}
		let c = this._root_item.element();
		let cl = [];
		for (let i = 0; i < c.classList.length; ++i) {
			if (c.classList[i].startsWith("state-"))
				cl.push(c.classList[i]);
		}
		c.setAttribute("class", "root-list-block" + (cl.length >0 && (" " + cl.join(" ")) || ""));
		return c;
	}

	_list_container() {
		let c = document.createElement("div");
		c.setAttribute("class", "list-container hidden");
		c.appendChild(this._make_list_item_elements());
		this._list_element = c;
		return c;
	}

	_make_root_columns(data) {
	}

	_make_group_list(data) {
		this._item_groups = data.groups.map(function(gd) {
			return this._make_group_item(gd);
		}, this);
	}

	_make_group_item(gr_data) {
		return new ListBoxItemGroup(gr_data);
	}

	_make_list_item_elements() {
		let fr = document.createDocumentFragment();
		this._item_groups.forEach(function(ig) {
			fr.appendChild(ig.element());
		});
		return fr;
	}
}

class DatabaseListBox extends DropdownListBox {
	constructor(menu) {
		super();
		this._menu     = menu;
		this._name     = null;
		this._type     = null;
		this._correct  = false;
		this._message  = null;
		this._location = null;
	}

	set_data(data) {
		this._name     = data.root.name;
		this._type     = data.root.type;
		this._correct  = data.root.correct;
		this._message  = data.root.message;
		this._location = data.root.location;
		super.set_data(data);
	}

	_make_root_columns(data) {
		this._root_item.state(this._correct && "green" || "red");
		this._root_item.add_column(new StatusIndicator(this._name, this._message, "title-item-wrap"));
		this._root_item.add_column(new ListBoxColumn(this._message, null, "message-item state-text"));
		this._root_item.add_column(new ListBoxColumn(this._type, null, "db-type"));
		this._root_item.add_column(new ListBoxColumn(this._location, null, "db-location"));
		if (this._menu)
			this._root_item.add_column(new ListBoxColumn(this._menu, null, "db-menu-button"));
	}

	_make_group_item(gr_data) {
		return new DatabaseItemGroup(gr_data);
	}
}

class SourceListBox extends DropdownListBox {
	element() {
		let _new = !this._element && true || false;
		super.element();
		if (_new) {
			let that = this;
			this._element.addEventListener("click", function(event) {
				if (event.target.tagName == "BUTTON") {
					let p = event.target.closest("div[data-id]")
					if (p) {
						let id = parseInt(p.getAttribute("data-id"));
						let type = p.getAttribute("data-type");
						that._check_button_clicked(id, type, event.target);
					}
				}
			});
		}
		return this._element;
	}

	_make_root_columns(data) {
		let count = this._item_groups.reduce(function(cnt, gr) {
			return cnt + gr.count();
		}, 0);
		let enabled = (count > 0);
		this._root_item.state(enabled && "green" || "gray");
		this._root_item.add_column(new StatusIndicator("Total sources: " + count, enabled && "Enabled" || "Disabled"));
	}

	_make_group_item(gr_data) {
		return new SourceItemGroup(gr_data);
	}

	_check_button_clicked(id, type, btn) {
		let that = this;
		let state = "yellow";
		let btn_text = btn.textContent;
		btn.textContent = "Checking...";
		btn.disabled = true;
		window.fetch("admin.php", {
			method: "POST",
			cache: "no-store",
			headers: Object.assign(HTTP_HEADERS, HTTP_HEADERS_POST),
			credentials: "same-origin",
			body: JSON.stringify({ cmd: "checksource", id: id, type: type })
		}).then(function(resp) {
			if (!resp.ok)
				throw new Error(`Failed to send checking request (${resp.status})`);
			return resp.json();
		}).then(function(data) {
			Common.checkResult(data);
			let msg = [ data.message ];
			if (data.status) {
				if (type === "mailbox") {
					msg.push("Messages: " + data.status.messages);
					msg.push("Unseen: " + data.status.unseen);
				}
				else if (type === "directory" || type === "remotefs") {
					msg.push("Files: " + data.status.files);
				}
			}
			Notification.add({ text: msg, type: "info" });
			state = "green";
		}).catch(function(err) {
			Common.displayError(err);
			Notification.add({ text: err.message, type: "error" });
		}).finally(function() {
			btn.textContent = btn_text;
			btn.disabled = false;
			that._set_state(state, id, type);
		});
	}

	_set_state(state, id, type) {
		let flag = 0;
		let gstate = "green";
		for (let i = 0; flag !== 3 && i < this._item_groups.length; ++i) {
			let gr = this._item_groups[i];
			if (!(flag & 1) && gr.type() === type) {
				gr.state(state, id);
				flag |= 1;
			}
			if (!(flag & 2)) {
				let s = gr.state();
				if (s !== "green") {
					gstate = s;
					flag |= 2;
				}
			}
		}
		this._root_item.state(gstate);
	}
}

class ListBoxItem {
	constructor() {
		this._state = null;
		this._element = null;
		this._columns = [];
	}

	add_column(col) {
		this._columns.push(col);
	}

	element() {
		if (!this._element) {
			this._element = document.createElement("div");
			let extra_class = "";
			if (this._state) {
				extra_class = " state-" + this._state;
			}
			this._element.setAttribute("class", "block-list-item round-border" + extra_class);
			this._insert_column_elements();
		}
		return this._element;
	}

	state(state) {
		if (!state) {
			return this._state;
		}

		if (this._element) {
			if (this._state) {
				this._element.classList.remove("state-" + this._state);
			}
			this._element.classList.add("state-" + state);
		}
		this._state = state;
	}

	_insert_column_elements() {
		this._columns.forEach(function(c) {
			this._element.appendChild(c.element());
		}, this);
	}
}

class SourceListItem extends ListBoxItem {
	constructor(data) {
		super();
		this._id = data.id;
		this._type = data.type;
	}

	id() {
		return this._id;
	}

	type() {
		return this._type;
	}

	element() {
		let el = super.element();
		el.setAttribute("data-id", this._id);
		el.setAttribute("data-type", this._type);
		return el;
	}
}

class ListBoxItemGroup {
	constructor(data) {
		this._name = data.name;
		this._type = data.type;
		this._element = null;
		this._items = data.items.map(function(it) {
			return this._make_item(it);
		}, this);
	}

	type() {
		return this._type;
	}

	count() {
		return this._items.length;
	}

	element() {
		if (!this._element) {
			let fr = document.createDocumentFragment();
			let h = document.createElement("h5");
			h.appendChild(document.createTextNode(this._name + " (" + this._items.length + ")"));
			fr.appendChild(h);
			this._items.forEach(function(it) {
				fr.appendChild(it.element());
			});
			this._element = fr;
		}
		return this._element;
	}

	_make_item(d) {
		return new ListBoxItem();
	}
}

class DatabaseItemGroup extends ListBoxItemGroup {
	_make_item(d) {
		let it = super._make_item(d);
		let state = d.error_code && "red" || (d.message === "Ok" && "green" || "yellow");
		it.state(state);
		it.add_column(new StatusIndicator(d.name, d.message, "title-item-wrap"));
		it.add_column(new ListBoxColumn(d.engine || d.message, null, "message-item state-text"));
		it.add_column(new ListBoxColumn(d.rows || 0, "Records", "dbtable-records"));
		it.add_column(new ListBoxColumn((d.data_length || 0) + (d.index_length || 0), "Size", "dbtable-size"));
		return it;
	}
}

class SourceItemGroup extends ListBoxItemGroup {
	state(new_state, item_id) {
		if (item_id !== undefined) {
			this._items.find(function(item) {
				if (item.id() == item_id) {
					item.state(new_state);
					return true;
				}
				return false;
			});
			return;
		}
		let gstate = "green";
		for (let i = 0; i < this._items.length; ++i) {
			let state = this._items[i].state();
			if (state !== gstate) {
				return state;
			}
		}
		return gstate;
	}

	_make_item(d) {
		let it = new SourceListItem({ id: d.id, type: this._type });
		it.state("green");
		it.add_column(new StatusIndicator(d.name, null, "title-item-wrap"));
		if (this._type === "mailbox") {
			it.add_column(new ListBoxColumn(d.mailbox, null, "mailbox-location"));
			it.add_column(new ListBoxColumn(d.host, "Host", "mailbox-host"));
		}
		else {
			it.add_column(new ListBoxColumn(d.location, null, "directory-location"));
		}
		it.add_column(new ListBoxColumn(this._make_check_button(), null, "source-check-button"));
		return it;
	}

	_make_check_button() {
		let btn = document.createElement("button");
		btn.appendChild(document.createTextNode("Check accessibility"));
		return btn;
	}

}

class ListBoxColumn {
	constructor(value, title, class_string) {
		this._value = value;
		this._title = title;
		this._class = class_string;
		this._element = null;
	}

	element() {
		if (!this._element) {
			this._element = document.createElement("div");
			this._element.setAttribute("class", "block-item-column" + (this._class && (" " + this._class) || ""));
			this._add_children();
		}
		return this._element;
	}

	_add_children() {
		let val_el = this._element;
		if (this._title) {
			let sp = document.createElement("span");
			sp.appendChild(document.createTextNode(this._title + ":"));
			this._element.appendChild(sp);
			val_el = document.createElement("span");
			val_el.setAttribute("class", "value");
			this._element.appendChild(val_el);
		}
		if (typeof(this._value) != "object")
			val_el.appendChild(document.createTextNode(this._value));
		else
			val_el.appendChild(this._value);
	}
}

class StatusIndicator extends ListBoxColumn {
	_add_children() {
		let div = document.createElement("div");
		div.setAttribute("class", "state-background status-indicator");
		if (this._title) {
			div.setAttribute("title", this._title);
		}
		this._element.appendChild(div);
		if (this._value) {
			div = document.createElement("div");
			div.appendChild(document.createTextNode(this._value));
			this._element.appendChild(div);
		}
	}
}
