/**
 * dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
 * Copyright (C) 2021-2025 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 DomainList {
	constructor() {
		this._page = null;
		this._table = null;
		this._scroll = null;
		this._element = document.getElementById("main-block");
		this._sort = { column: "fqdn", direction: "ascent" };
	}

	display() {
		this._make_page_container();
		this._make_scroll_container();
		this._make_table();
		this._scroll.append(this._table.element());
		this._page.append(this._scroll);
		this._element.append(this._page);
		this._table.focus();
	}

	update() {
		this._fetch_list();
	}

	title() {
		return "Domain List";
	}

	_fetch_list() {
		this._table.display_status("wait");
		let that = this;

		return window.fetch("domains.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 domain list");
			return resp.json();
		}).then(function(data) {
			that._table.display_status(null);
			Common.checkResult(data);
			let d = { more: data.more };
			d.rows = data.domains.map(function(it) {
				return that._make_row_data(it);
			});
			if (User.level === "admin" || User.level === "manager") {
				d.rows.push(new NewDomainRow(4));
			}
			let fr = new DomainFrame(d, that._table.last_row_index() + 1);
			that._table.clear();
			that._table.add_frame(fr);
			if (that._sort.column) {
				that._table.sort(that._sort.column, that._sort.direction);
			}
			that._table.focus();
		}).catch(function(err) {
			Common.displayError(err);
			that._table.display_status("error");
		});
	}

	_make_page_container() {
		this._page = document.createElement("div");
		this._page.classList.add("page-container");
	}

	_make_scroll_container() {
		this._scroll = document.createElement("div");
		this._scroll.classList.add("table-wrapper");
	}

	_make_table() {
		this._table = new ITable({
			class:   "main-table domains",
			onclick: function(row) {
				let data = row.userdata();
				if (data) {
					this._display_edit_dialog(data);
				}
			}.bind(this),
			onsort: function(col) {
				let dir = col.sorted() && "toggle" || "ascent";
				this._table.set_sorted(col.name(), dir);
				this._table.sort(col.name(), col.sorted());
				this._sort.column = col.name();
				this._sort.direction = col.sorted();
				this._table.focus();
			}.bind(this),
			onfocus: function(el) {
				scroll_to_element(el, this._scroll);
			}.bind(this)
		});
		[
			{ content: "", sortable: true, name: "status", class: "cell-status" },
			{ content: "FQDN", sortable: true, name: "fqdn" },
			{ content: "Updated", sortable: true, name: "date" },
			{ content: "Description", class: "descr" }
		].forEach(function(col) {
			let c = this._table.add_column(col);
			if (c.name() === this._sort.column) {
				c.sort(this._sort.direction);
			}
		}, this);
	}

	_make_row_data(d) {
		let rd = { cells: [], userdata: d.fqdn };
		rd.cells.push(new DomainStatusCell(d.active));
		rd.cells.push({ content: d.fqdn, class: "fqdn" });
		rd.cells.push(new DomainTimeCell(new Date(d.updated_time)));
		rd.cells.push({ content: d.description || "", class: "descr" });
		return rd;
	}

	_display_edit_dialog(fqdn) {
		let dlg_par = {};
		if (fqdn === "*new") {
			dlg_par["new"]  = true;
			dlg_par.disable = User.level !== "admin";
		}
		else {
			dlg_par.fqdn    = fqdn;
			dlg_par.disable = User.level === "user";
		}
		dlg_par.level = User.level;

		let dlg = new DomainEditDialog(dlg_par);
		this._element.appendChild(dlg.element());
		let that = this;
		dlg.show().then(function(d) {
			if (d) {
				that.update();
			}
		}).finally(function() {
			dlg.element().remove();
			that._table.focus();
		});
	}
}

class DomainStatusCell extends ITableCell {
	constructor(is_active, props) {
		props = props || {};
		let ca = (props.class || "").split(" ");
		ca.push(is_active && "state-green" || "state-gray");
		props.class = ca.filter(function(s) { return s.length > 0; }).join(" ");
		super(is_active, props);
	}

	value(target) {
		if (target === "dom") {
			let div = document.createElement("div");
			div.setAttribute("class", "state-background status-indicator");
			if (!this._title) {
				div.setAttribute("title", this._content && "active" || "inactive");
			}
			return div;
		}
		return this._content;
	}
}

class DomainTimeCell extends ITableCell {
	value(target) {
		if (target === "dom") {
			return this._content && this._content.toUIString() || "";
		}
		if (target === "sort") {
			return this._content && this._content.valueOf() || "";
		}
		super.value(target);
	}
}

class NewDomainRow extends ITableRow {
	constructor(col_cnt) {
		super({
			userdata: "*new",
			cells:    []
		});
		this._col_cnt = col_cnt;
	}

	element() {
		if (!this._element) {
			super.element();
			this._element.classList.add("colspanned", "virtual-item");
			for (let i = 0; i < this._col_cnt; ++i) {
				let cell = document.createElement("div");
				cell.setAttribute("class", "table-cell");
				cell.appendChild(document.createTextNode(!i && "New domain" || "\u00A0"));
				this._element.appendChild(cell);
			}
		}
		return this._element;
	}
}

class DomainFrame extends ITableFrame {
	sort(col_idx, direction) {
		this._sort_dir = (direction === "ascent" && 1) || (direction === "descent" && 2) || 0;
		super.sort(col_idx, direction);
	}

	_compare_cells(c1, c2) {
		if (!c1) {
			return this._sort_dir === 2;
		}
		if (!c2) {
			return this._sort_dir === 1;
		}
		return super._compare_cells(c1, c2);
	}
}

class DomainEditDialog extends VerticalDialog {
	constructor(params) {
		let tl = null;
		let ba = [];
		if (params.level !== "user") ba.push("save");
		if (params["new"]) {
			tl = "New domain";
		}
		else {
			tl = "Domain settings";
			if (params.level !== "user") ba.push("delete");
		}
		ba.push("close");
		super({ title: tl, buttons: ba });
		this._data    = params || {};
		this._note_el = null;
		this._fqdn_el = null;
		this._actv_el = null;
		this._desc_el = null;
		this._c_tm_el = null;
		this._u_tm_el = null;
		this._fetched = false;
	}

	_gen_content() {
		let dis = this._data.disable;

		this._note_el = this._content.appendChild(document.createElement("div"));
		this._note_el.classList.add("hidden", "warn-block");

		let fq = document.createElement("input");
		fq.setAttribute("type", "text");
		if (!this._data["new"]) {
			fq.setAttribute("value", this._data.fqdn);
			fq.disabled = true;
		}
		fq.required = true;
		this._insert_input_row("FQDN", fq);
		this._fqdn_el = fq;

		{
			let en = document.createElement("select");
			let op1 = document.createElement("option");
			op1.setAttribute("value", "yes");
			op1.appendChild(document.createTextNode("Yes"));
			en.appendChild(op1);
			let op2 = document.createElement("option");
			op2.setAttribute("value", "no");
			op2.appendChild(document.createTextNode("No"));
			en.appendChild(op2);
			en.required = true;
			en.disabled = dis;
			this._insert_input_row("Active", en);
			this._actv_el = en;
		}

		let tx = document.createElement("textarea");
		tx.classList.add("description")
		tx.disabled = dis;
		this._insert_input_row("Description", tx);
		this._desc_el = tx;

		let ct = document.createElement("input");
		ct.setAttribute("type", "text");
		ct.disabled = true;
		ct.setAttribute("value","n/a");
		this._insert_input_row("Created", ct);
		this._c_tm_el = ct;

		let ut = document.createElement("input");
		ut.setAttribute("type", "text");
		ut.setAttribute("value","n/a");
		ut.disabled = true;
		this._insert_input_row("Updated", ut);
		this._u_tm_el = ut;

		this._inputs.addEventListener("input", function(event) {
			if (this._fetched || this._data["new"]) {
				this._buttons[1].disabled = (
					this._fqdn_el.value.trim() === "" || (
					this._actv_el.dataset.server === this._actv_el.value &&
					this._desc_el.defaultValue === this._desc_el.value &&
					this._fqdn_el.defaultValue === this._fqdn_el.value)
				);
			}
		}.bind(this));

		if (!this._fetched) this._fetch_data();
	}

	_fetch_data() {
		this._enable_ui(false);
		this.display_status("wait", "Getting data...");

		const url = new URL("domains.php", document.location);
		url.searchParams.set("domain", this._data.fqdn || "");
		window.fetch(url, {
			method: "GET",
			cache: "no-store",
			headers: HTTP_HEADERS,
			credentials: "same-origin"
		}).then(resp => {
			if (!resp.ok) throw new Error("Failed to fetch the domain data");
			return resp.json();
		}).then(data => {
			this._fetched = true;
			Common.checkResult(data);
			if (data.created_time) data.created_time = new Date(data.created_time);
			if (data.updated_time) data.updated_time = new Date(data.updated_time);
			this._update_ui(data);
			this._enable_ui(true);
		}).catch(err => {
			Common.displayError(err);
			this.display_status("error", err.message);
		}).finally(() => {
			this.display_status("wait", null);
		});
	}

	_update_ui(data) {
		if (data.verification === "dns") {
			this._note_el.classList.remove("hidden");
			let lm = document.createElement("a");
			lm.href = "";
			lm.textContent = "Learn more";
			this._note_el.append("Domain verification is required! ", lm);
			lm.addEventListener("click", event => {
				event.preventDefault();
				this._display_verification_dialog(data.verification_data, lm);
			});
		}
		if (this._data["new"]) return;

		let val = "";
		for (let i = 0; i < this._actv_el.options.length; ++i) {
			let op = this._actv_el.options[i];
			if (data.active === (op.value === "yes")) {
				op.setAttribute("selected", "selected");
				val = op.value;
			}
			else {
				op.removeAttribute("selected");
			}
		}
		this._actv_el.value = val;
		this._actv_el.dataset.server = val;
		this._desc_el.appendChild(document.createTextNode(data.description || ""));
		this._c_tm_el.setAttribute("value", data.created_time && data.created_time.toUIString() || "n/a");
		this._u_tm_el.setAttribute("value", data.updated_time && data.updated_time.toUIString() || "n/a");
	}

	_add_button(container, text, type) {
		let btn = null;
		if (type === "save") {
			text = "Save";
			btn = document.createElement("button");
			btn.disabled = true;
			btn.addEventListener("click", this._save.bind(this));
		}
		else if (type === "delete") {
			text = "Delete";
			btn = document.createElement("button");
			btn.addEventListener("click", () => this._confirm_delete(false));
		}
		else {
			super._add_button(container, text, type);
			return;
		}
		btn.setAttribute("type", "button");
		btn.appendChild(document.createTextNode(text));
		container.appendChild(btn);
		this._buttons.push(btn);
	}

	_enable_ui(en) {
		let dis = this._data.disable;
		this._fqdn_el.disabled = !en || !this._data["new"];
		this._actv_el.disabled = !en || dis;
		this._desc_el.disabled = !en || dis;
		for (let i = 2; i < this._buttons.length - 1; ++i) {
			this._buttons[i].disabled = !en;
		}
		this.focus();
	}

	_save() {
		this._enable_ui(false);
		this.display_status("wait", "Sending data to the server...");

		const body = {};
		body.fqdn        = this._data["new"] && this._fqdn_el.value || this._data.fqdn;
		body.action      = this._data["new"] && "add" || "update";
		body.active      = this._actv_el.value === "yes";
		body.description = this._desc_el.value;

		window.fetch("domains.php", {
			method: "POST",
			cache: "no-store",
			headers: Object.assign(HTTP_HEADERS, HTTP_HEADERS_POST),
			credentials: "same-origin",
			body: JSON.stringify(body)
		}).then(resp => {
			if (!resp.ok)
				throw new Error("Failed to " + (body.new && "add" || "update") + " the domain data");
			return resp.json();
		}).then(data => {
			Common.checkResult(data);
			this._result = body;
			this.hide();
			Notification.add({
				text: "The domain " + body.fqdn + " was " + (body.action === "add" && "added" || "updated")
			});
		}).catch(err => {
			Common.displayError(err);
			this.display_status("error", err.message);
		}).finally(() => {
			this.display_status("wait", null);
			this._enable_ui(true);
		});
	}

	_confirm_delete(with_reports) {
		let msg;
		if (with_reports) {
			msg = "There are incoming reports for the domain. Do you want to delete the domain with its reports?";
		} else {
			msg = "Are sure you want to delete this domain?";
		}
		if (confirm(msg)) {
			this._delete(with_reports);
			return true;
		}
	}

	_delete(with_reports) {
		this._enable_ui(false);
		this.display_status("wait", "Sending a request to the server...");

		const body = {
			fqdn:   this._data.fqdn,
			force:  with_reports,
			action: "delete"
		};

		window.fetch("domains.php", {
			method: "POST",
			cache: "no-store",
			headers: Object.assign(HTTP_HEADERS, HTTP_HEADERS_POST),
			credentials: "same-origin",
			body: JSON.stringify(body)
		}).then(resp => {
			if (!resp.ok) throw new Error("Failed to delete the domain");
			return resp.json();
		}).then(data => {
			Common.checkResult(data);
			this._result = data;
			this.hide();
			Notification.add({ text: "The domain " + body.fqdn + " was removed" });
		}).catch(err => {
			if (err.error_code === -10 && this._confirm_delete(true)) return;
			Common.displayError(err);
			this.display_status("error", err.message);
		}).finally(() => {
			this.display_status("wait", null);
			this._enable_ui(true);
		});
	}

	_display_verification_dialog(data, source_el) {
		const dlg = new VerificationDialog(data);
		this._element.classList.add("hidden");
		document.getElementById("main-block").append(dlg.element());
		dlg.show().finally(() => {
			dlg.element().remove();
			this._element.classList.remove("hidden");
			source_el.focus();
		});
	}
}

class VerificationDialog extends ModalDialog {
	constructor(data) {
		super({ title: "Domain verification information", buttons: [ "close" ] });
		this._data = data;
	}

	_gen_content() {
		this._element.children[0].classList.add("verification");
		this._content.classList.add("vertical-content");
		const t1 = document.createElement("div");
		t1.textContent = "To ensure that you are the owner of the domain, add a TXT record to the DNS settings at your domain registrar with the following content:";
		const t2 = document.createElement("textarea");
		t2.textContent = this._data;
		t2.readOnly = true;
		const t3 = document.createElement("div");
		t3.textContent = "Once the domain has been successfully added, the TXT record can be deleted.";
		const t4 = document.createElement("div");
		t4.textContent = "Important! Some registrars may require additional time to publish your verification code. If the tool can't find your new TXT record, wait an hour before you try again."
		this._content.append(t1, t2, t3, t4);
	}
}
