package org.javastack.sftpserver;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.PublicKey;
import java.util.Collection;
import java.util.Map;

import org.apache.sshd.common.io.IoSession;
import org.apache.sshd.common.session.Session;
import org.apache.sshd.common.session.SessionListener;
import org.apache.sshd.common.util.logging.AbstractLoggingBean;
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.subsystem.sftp.Handle;
import org.apache.sshd.server.subsystem.sftp.SftpEventListener;
import org.slf4j.event.Level;

public class ServiceLogger extends AbstractLoggingBean implements SftpEventListener, SessionListener {
	private boolean logRequest = false;

	ServiceLogger() {
		super();
	}

	public void setLogRequest(final boolean logRequest) {
		this.logRequest = logRequest;
	}

	private static final String toHuman(final Session session, final String username) {
		final IoSession networkSession = session.getIoSession();
		final SocketAddress peerAddress = (networkSession == null) ? null : networkSession.getRemoteAddress();
		return (username == null ? "<unknown>" : username) + "@" + addrToHuman(peerAddress);
	}

	private static final String toHuman(final Session session) {
		return toHuman(session, session.getUsername());
	}

	private static final String addrToHuman(final SocketAddress addr) {
		if (addr instanceof InetSocketAddress) {
			final InetSocketAddress a = (InetSocketAddress) addr;
			return (a.getHostString() + ":" + a.getPort());
		}
		return String.valueOf(addr);
	}

	private final void logLevel(final String msg, final Level level) {
		switch (level) {
		case INFO:
			log.info(msg);
			break;
		case ERROR:
			log.error(msg);
			break;
		case WARN:
			log.warn(msg);
			break;
		case DEBUG:
			log.debug(msg);
			break;
		case TRACE:
			log.trace(msg);
			break;
		}
	}

	// Auth Logger

	public void authPasswordPreLogin(final ServerSession session, final String username) {
		log.info("auth password(" + toHuman(session, username) + ")");
	}

	public void authPublicKeyPreLogin(final ServerSession session, final String username, final PublicKey key) {
		final String keyType = (key == null ? "<unknown>" : key.getAlgorithm());
		log.info("auth publickey(" + toHuman(session, username) + ") type: " + keyType);
	}

	public void authPasswordPostLogin(final ServerSession session, final String username, final Level level,
			final String info) {
		logLevel("auth password(" + toHuman(session, username) + ") info: " + info, level);
	}

	public void authPublicKeyPostLogin(final ServerSession session, final String username, final PublicKey key,
			final Level level, final String info) {
		final String keyType = (key == null ? "<unknown>" : key.getAlgorithm());
		logLevel("auth publickey(" + toHuman(session, username) + ") type: " + keyType //
				+ " info: " + info, level);
	}

	// Session Logger

	@Override
	public void sessionCreated(final Session session) {
		if (log.isInfoEnabled()) {
			log.info("session created(" + toHuman(session) + ")");
		}
	}

	@Override
	public void sessionDisconnect(final Session session, final int reason, final String msg, final String language,
			final boolean initiator) {
		if (log.isInfoEnabled()) {
			log.info("session disconnect(" + toHuman(session) + ") reason: " + reason + " msg: " + msg);
		}
	}

	@Override
	public void sessionClosed(final Session session) {
		if (log.isInfoEnabled()) {
			log.info("session closed(" + toHuman(session) + ")");
		}
	}

	// Request Logger

	@Override
	public void initialized(final ServerSession session, final int version) {
		if (log.isInfoEnabled()) {
			log.info("request initialized(" + toHuman(session) + ") version: " + version);
		}
	}

	@Override
	public void destroying(final ServerSession session) {
		if (log.isInfoEnabled()) {
			log.info("request destroying(" + toHuman(session) + ")");
		}
	}

	@Override
	public void opening(final ServerSession session, final String remoteHandle, final Handle localHandle)
			throws IOException {
		if (!logRequest)
			return;
		if (log.isInfoEnabled()) {
			final Path path = localHandle.getFile();
			log.info("request opening(" + toHuman(session) + ")[" + remoteHandle + "][" //
					+ (Files.isDirectory(path) ? "directory" : "file") + "] " + path);
		}
	}

	@Override
	public void closing(final ServerSession session, final String remoteHandle, final Handle localHandle) {
		if (!logRequest)
			return;
		if (log.isInfoEnabled()) {
			final Path path = localHandle.getFile();
			log.info("request close(" + toHuman(session) + ")[" + remoteHandle + "][" //
					+ (Files.isDirectory(path) ? "dir" : "file") + "] " + path);
		}
	}

	@Override
	public void creating(final ServerSession session, final Path path, final Map<String, ?> attrs) throws IOException {
		if (!logRequest)
			return;
		if (log.isInfoEnabled()) {
			log.info("request creating(" + toHuman(session) + ") " + path);
		}
	}

	@Override
	public void moving(final ServerSession session, final Path srcPath, final Path dstPath,
			final Collection<CopyOption> opts) throws IOException {
		if (!logRequest)
			return;
		if (log.isInfoEnabled()) {
			log.info("request moving(" + toHuman(session) + ")[" //
					+ (Files.isDirectory(srcPath) ? "dir" : "file") + "]" //
					+ opts + " " + srcPath + " => " + dstPath);
		}
	}

	@Override
	public void removing(final ServerSession session, final Path path, final boolean isDirectory) throws IOException {
		if (!logRequest)
			return;
		if (log.isInfoEnabled()) {
			log.info("request removing(" + toHuman(session) + ")[" + (isDirectory ? "dir" : "file") + "] " + path);
		}
	}

	@Override
	public void linking(final ServerSession session, final Path source, final Path target, final boolean symLink)
			throws IOException {
		if (!logRequest)
			return;
		// Must be fail
		if (log.isInfoEnabled()) {
			log.info("request linking(" + toHuman(session) + ")[" + (symLink ? "sym" : "hard") + "] " //
					+ source + " => " + target);
		}
	}

	@Override
	public void modifyingAttributes(final ServerSession session, final Path path, final Map<String, ?> attrs)
			throws IOException {
		if (!logRequest)
			return;
		// Must be fail
		if (log.isInfoEnabled()) {
			log.info("request modifyingAttributes(" + toHuman(session) + ") " + path + ": " + attrs);
		}
	}
}