/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.focus.telemetry.startuptelemetry

import android.app.Activity
import android.app.Application
import android.os.Bundle
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.Companion.NONE
import androidx.annotation.VisibleForTesting.Companion.PRIVATE
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import mozilla.components.support.base.log.Log
import mozilla.components.support.base.log.logger.Logger

private val logger = Logger("StartupActivityLog")

/**
 * A record of the [Activity] created, started, and stopped events as well as [Application]
 * foreground and background events. See [log] for the log. This class is expected to be
 * registered in [Application.onCreate] by calling [registerInAppOnCreate].
 *
 * To prevent this list from growing infinitely, we clear the list when the application is stopped.
 * This is acceptable from the current requirements: we never need to inspect more than the current
 * start up.
 */
class StartupActivityLog {

    private val _log = mutableListOf<LogEntry>()
    val log: List<LogEntry> = _log

    /**
     * Registers the activity and process lifecycle observers.
     */
    fun registerInAppOnCreate(
        application: Application,
        processLifecycleOwner: LifecycleOwner = ProcessLifecycleOwner.get(),
    ) {
        processLifecycleOwner.lifecycle.addObserver(StartupLogAppLifecycleObserver())
        application.registerActivityLifecycleCallbacks(StartupLogActivityLifecycleCallbacks())
    }

    /**
     * Returns the app and activity lifecycle observers for testing.
     */
    @VisibleForTesting(otherwise = NONE)
    fun getObserversForTesting() = Pair(StartupLogAppLifecycleObserver(), StartupLogActivityLifecycleCallbacks())

    /**
     * Logs the captured startup entries.
     */
    @VisibleForTesting(otherwise = PRIVATE)
    fun logEntries(loggerArg: Logger = logger, logLevel: Log.Priority = Log.logLevel) {
        // Optimization: we want to avoid the potentially expensive conversions
        // to Strings if we're not going to log anyway.
        if (logLevel > Log.Priority.DEBUG) {
            return
        }

        val transformedEntries = log.map {
            when (it) {
                is LogEntry.AppStarted -> "App-STARTED"
                is LogEntry.AppStopped -> "App-STOPPED"
                is LogEntry.ActivityCreated -> "${it.activityClass.simpleName}-CREATED"
                is LogEntry.ActivityStarted -> "${it.activityClass.simpleName}-STARTED"
                is LogEntry.ActivityStopped -> "${it.activityClass.simpleName}-STOPPED"
            }
        }

        loggerArg.debug(transformedEntries.toString())
    }

    /**
     * [DefaultLifecycleObserver] that logs app-level start and stop events.
     */
    @VisibleForTesting(otherwise = PRIVATE)
    inner class StartupLogAppLifecycleObserver : DefaultLifecycleObserver {
        override fun onStart(owner: LifecycleOwner) {
            _log.add(LogEntry.AppStarted)
        }

        override fun onStop(owner: LifecycleOwner) {
            logEntries()
            _log.clear() // Optimization: see class kdoc for details.
            _log.add(LogEntry.AppStopped)
        }
    }

    /**
     * [DefaultActivityLifecycleCallbacks] that logs activity-level events.
     */
    @VisibleForTesting(otherwise = PRIVATE)
    inner class StartupLogActivityLifecycleCallbacks : DefaultActivityLifecycleCallbacks {
        override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
            _log.add(LogEntry.ActivityCreated(activity::class.java))
        }

        override fun onActivityStarted(activity: Activity) {
            _log.add(LogEntry.ActivityStarted(activity::class.java))
        }

        override fun onActivityStopped(activity: Activity) {
            _log.add(LogEntry.ActivityStopped(activity::class.java))
        }
    }

    /**
     * A log entry with its detailed information for the [StartupActivityLog].
     */
    sealed class LogEntry {
        /**
         * Represents an application-started event.
         */
        object AppStarted : LogEntry()

        /**
         * Represents an application-stopped event.
         */
        object AppStopped : LogEntry()

        /**
         * Represents an activity-created event.
         */
        data class ActivityCreated(val activityClass: Class<out Activity>) : LogEntry()

        /**
         * Represents an activity-started event.
         */
        data class ActivityStarted(val activityClass: Class<out Activity>) : LogEntry()

        /**
         * Represents an activity-stopped event.
         */
        data class ActivityStopped(val activityClass: Class<out Activity>) : LogEntry()
    }
}
