import { Entry, Har, PostData, Request, Response } from "har-format";
import { render, Component } from "preact";

type MyState = {
	finished_entries: Entry[];
	unfinished_entries: Map<string, Entry>;
};

class TrafficLog extends Component {
	connection: WebSocket | undefined;

	state: MyState = { finished_entries: [], unfinished_entries: new Map() };

	constructor() {
		super();
	}

	componentDidMount() {
		// This should also be dynamic
		this.connection = new WebSocket("ws://localhost:10001");
		this.connection.onmessage = (msg) => {
			this.process_msg(msg.data);
			this.setState({
				finished_entries: this.state.finished_entries,
				unfinished_entries: this.state.unfinished_entries,
			});
		};
		this.connection.onclose = this.connection.onerror = () => {
			window.location.reload();
		};
	}

	render() {
		const download_har = () => {
			var tempLink = document.createElement("a");
			var taBlob = new Blob([JSON.stringify(this.export_har())], {
				type: "text/plain",
			});

			tempLink.setAttribute("href", URL.createObjectURL(taBlob));
			tempLink.setAttribute("download", `rentgendroid-capture.har`);

			tempLink.click();

			URL.revokeObjectURL(tempLink.href);
		};

		const inspect_har = async () => {
			const req_body = {
				har: this.export_har(),
				private_data: [
					[
						"adid",
						document.getElementById("adid_priv_info_table")!
							.textContent,
					],
					[
						"latitude",
						document.getElementById("lat_priv_info_table")!
							.textContent,
					],
					[
						"longitude",
						document.getElementById("lon_priv_info_table")!
							.textContent,
					],
				],
			};

			const resp = await fetch("/inspect_har", {
				method: "POST",
				body: JSON.stringify(req_body),
			});
			const resp_text = await resp.text();
			const newWindow = window.open();
			newWindow?.document.write(resp_text);
			newWindow?.document.close();
		};

		const contentWithLineBreaks = this.state.finished_entries.map((req) => {
			return (
				<span>
					{req.request.url}
					<br />
				</span>
			);
		});

		return (
			<div>
				<button onClick={download_har}>Download HAR</button>
				<button onClick={inspect_har}>Inspect HAR</button>
				<div>
					<h2>stats: </h2>
					<p>
						Request + responce pairs:{" "}
						{this.state.finished_entries.length}
					</p>
					<p>
						Waiting for the responce:{" "}
						{this.state.unfinished_entries.size}
					</p>
				</div>
				<div>{contentWithLineBreaks}</div>
			</div>
		);
	}

	process_msg(s: string) {
		let obj = JSON.parse(s);
		console.log(obj);

		if (obj.type !== "data") return;
		if (obj.payload && obj.payload.data && obj.payload.data.requestReceived)
			this.process_req(obj.payload.data.requestReceived);
		if (
			obj.payload &&
			obj.payload.data &&
			obj.payload.data.responseCompleted
		)
			this.process_res(obj.payload.data.responseCompleted);
	}

	process_res(res: any) {
		let entry = this.state.unfinished_entries.get(res.id)!;

		let content_type = "application/text";
		let headers = JSON.parse(res.rawHeaders).map(
			(header: [string, string]) => {
				if (header[0].toLowerCase() === "content-type")
					content_type = header[1];
				return { name: header[0], value: header[1], comment: "" };
			}
		);

		//'{"startTime":1751745139334,
		// "startTimestamp":347666.762487,
		// "bodyReceivedTimestamp":347667.529477,
		// "headersSentTimestamp":347906.038202,
		// "responseSentTimestamp":347906.616067}'

		let timing_events = JSON.parse(res.timingEvents);

		let start_ts = timing_events.startTimestamp;
		let got_headers_ts = timing_events.headersSentTimestamp;
		let end_ts = timing_events.responseSentTimestamp;

		let wait_time = got_headers_ts - start_ts;
		let recieve_time = end_ts - got_headers_ts;

		let response: Response = {
			status: res.statusCode,
			statusText: res.statusMessage,
			httpVersion: entry.request.httpVersion,
			cookies: [],
			headers,
			content: {
				size: 0,
				mimeType: content_type,
				text: res.body,
				encoding: "base64",
			},
			redirectURL: "",
			headersSize: -1,
			bodySize: -1,
		};

		entry.response = response;
		entry.timings.wait = wait_time;
		entry.timings.receive = recieve_time;
		this.state.unfinished_entries.delete(res.id);
		this.state.finished_entries.push(entry);
	}

	process_req(req: any) {
		let content_type = "application/text";

		let headers = JSON.parse(req.rawHeaders).map(
			(header: [string, string]) => {
				if (header[0].toLowerCase() === "Content-Type")
					content_type = header[1];
				return { name: header[0], value: header[1], comment: "" };
			}
		);

		let timing_events = JSON.parse(req.timingEvents);

		let start_time: number = timing_events.startTime!;

		let start_datetime = new Date(start_time).toISOString();

		let request: Request = {
			method: req.method,
			url: req.url,
			httpVersion: req.httpVersion,
			cookies: [],
			headers,
			queryString: [],
			postData: req.body
				? ({ text: req.body, mimeType: content_type } as PostData)
				: undefined,
			headersSize: -1,
			bodySize: -1,
			comment: "",
		};

		//'{"startTime":1751745139334,"startTimestamp":347666.762487,"bodyReceivedTimestamp":347667.529477,"headersSentTimestamp":347906.038202,"responseSentTimestamp":347906.616067}'
		let entry: Entry = {
			startedDateTime: start_datetime,
			time: 0,
			request: request,
			response: {
				status: 0,
				statusText: "",
				httpVersion: "",
				cookies: [],
				headers: [],
				content: {
					size: 0,
					mimeType: "",
				},
				redirectURL: "",
				headersSize: 0,
				bodySize: 0,
			},
			cache: {},
			timings: {
				wait: 0,
				receive: 0,
			},
		};
		this.state.unfinished_entries.set(req.id, entry);
	}

	export_har(): Har {
		let ret: Har = {
			log: {
				version: "1.2",
				creator: {
					name: "Rentgendroid",
					version: "0.0.1",
				},
				entries: [
					...this.state.finished_entries,
					...this.state.unfinished_entries.values(),
				],
			},
		};
		return ret;
	}
}

render(<TrafficLog />, document.getElementById("traffic-log")!);
