package it.niedermann.nextcloud.deck;

import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

import it.niedermann.nextcloud.deck.util.MimeTypeUtil;

public class DeckLog {

    private DeckLog() {
        throw new UnsupportedOperationException("This class must not get instantiated");
    }

    private static final StringBuffer DEBUG_LOG = new StringBuffer();
    private static boolean PERSIST_LOGS = false;
    private static final String TAG = DeckLog.class.getSimpleName();
    private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public static void enablePersistentLogs(boolean persistLogs) {
        PERSIST_LOGS = persistLogs;
        if (!persistLogs) {
            clearDebugLog();
        }
    }

    public static String getStacktraceAsString(Throwable e) {
        final var sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw));
        return sw.toString();
    }

    public enum Severity {
        VERBOSE, DEBUG, LOG, INFO, WARN, ERROR, WTF
    }

    public static void verbose(Object... message) {
        log(Severity.VERBOSE, 4, message);
    }

    public static void log(Object... message) {
        log(Severity.DEBUG, 4, message);
    }

    public static void info(Object... message) {
        log(Severity.INFO, 4, message);
    }

    public static void warn(Object... message) {
        log(Severity.WARN, 4, message);
    }

    public static void error(Object... message) {
        log(Severity.ERROR, 4, message);
    }

    public static void wtf(Object... message) {
        log(Severity.WTF, 4, message);
    }

    public static void log(@NonNull Severity severity, Object... message) {
        log(severity, 3, message);
    }

    private static void log(@NonNull Severity severity, int stackTracePosition, Object... messages) {
        if (!(PERSIST_LOGS || BuildConfig.DEBUG)) {
            return;
        }
        final StackTraceElement caller = Thread.currentThread().getStackTrace()[stackTracePosition];
        final String print = "(" + caller.getFileName() + ":" + caller.getLineNumber() + ") " + caller.getMethodName() + "() → " + TextUtils.join(" ", messages);
        if (PERSIST_LOGS) {
            DEBUG_LOG
                    .append(dtf.format(Instant.now().atZone(ZoneId.systemDefault())))
                    .append(" ")
                    .append(severity.name())
                    .append(" ")
                    .append(print)
                    .append("\n");
        }
        switch (severity) {
            case DEBUG -> Log.d(TAG, print);
            case INFO -> Log.i(TAG, print);
            case WARN -> Log.w(TAG, print);
            case ERROR -> Log.e(TAG, print);
            case WTF -> Log.wtf(TAG, print);
            default -> Log.v(TAG, print);
        }
    }

    public static void logError(@Nullable Throwable e) {
        if (!(PERSIST_LOGS || BuildConfig.DEBUG)) {
            return;
        }
        if (e == null) {
            error("Could not log error because given error was null");
            return;
        }
        final StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw));
        final String stacktrace = sw.toString();
        final StackTraceElement caller = Thread.currentThread().getStackTrace()[3];
        final String print = "(" + caller.getFileName() + ":" + caller.getLineNumber() + ") " + caller.getMethodName() + "() → " + stacktrace;
        if (PERSIST_LOGS) {
            DEBUG_LOG.append(print).append("\n");
        }
        Log.e(TAG, print);
    }

    @NonNull
    public static String getDebugLog() {
        return DEBUG_LOG.toString();
    }

    public static void clearDebugLog() {
        DEBUG_LOG.setLength(0);
    }

    /**
     * Writes the current log to a temporary file and starts a share intent.
     */
    public static void shareLogAsFile(@NonNull Context context) throws IOException {
        Toast.makeText(context, R.string.copying_logs_to_file, Toast.LENGTH_LONG).show();
        final File logFile = new File(context.getCacheDir().getAbsolutePath() + "/log.txt");
        final FileWriter writer = new FileWriter(logFile);
        writer.write(DeckLog.getDebugLog());
        writer.close();
        context.startActivity(new Intent(Intent.ACTION_SEND)
                .putExtra(Intent.EXTRA_TITLE, context.getString(R.string.log_file))
                .putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", logFile))
                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                .setType(MimeTypeUtil.TEXT_PLAIN));
    }
}
