/* 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.fenix

import androidx.annotation.VisibleForTesting
import mozilla.components.browser.state.action.BrowserAction
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.action.DownloadAction
import mozilla.components.browser.state.action.TabListAction
import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.selector.normalTabs
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.MiddlewareContext
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry
import org.mozilla.fenix.utils.Settings

/**
 * [Middleware] to record telemetry in response to [BrowserAction]s.
 *
 * @property settings reference to the application [Settings].
 * @property adsTelemetry reference to [AdsTelemetry] use to record search telemetry.
 * @property metrics reference to the configured [MetricController] to record general page load events.
 */
class TelemetryMiddleware(
    private val settings: Settings,
    private val adsTelemetry: AdsTelemetry,
    private val metrics: MetricController
) : Middleware<BrowserState, BrowserAction> {

    private val logger = Logger("TelemetryMiddleware")

    @VisibleForTesting
    internal val redirectChains = mutableMapOf<String, RedirectChain>()

    /**
     * Utility to collect URLs / load requests in between location changes.
     */
    internal class RedirectChain(internal val root: String) {
        internal val chain = mutableListOf<String>()

        fun add(url: String) {
            chain.add(url)
        }
    }

    @Suppress("TooGenericExceptionCaught", "ComplexMethod")
    override fun invoke(
        context: MiddlewareContext<BrowserState, BrowserAction>,
        next: (BrowserAction) -> Unit,
        action: BrowserAction
    ) {
        // Pre process actions
        when (action) {
            is ContentAction.UpdateLoadingStateAction -> {
                context.state.findTab(action.sessionId)?.let { tab ->
                    // Record UriOpened event when a non-private page finishes loading
                    if (tab.content.loading && !action.loading && !tab.content.private) {
                        metrics.track(Event.UriOpened)
                    }
                }
            }
            is ContentAction.UpdateLoadRequestAction -> {
                context.state.findTab(action.sessionId)?.let { tab ->
                    // Collect all load requests in between location changes
                    if (!redirectChains.containsKey(action.sessionId) && action.loadRequest.url != tab.content.url) {
                        redirectChains[action.sessionId] = RedirectChain(tab.content.url)
                    }

                    redirectChains[action.sessionId]?.add(action.loadRequest.url)
                }
            }
            is ContentAction.UpdateUrlAction -> {
                redirectChains[action.sessionId]?.let {
                    // Record ads telemetry providing all redirects
                    try {
                        adsTelemetry.trackAdClickedMetric(it.root, it.chain)
                    } catch (t: Throwable) {
                        logger.info("Failed to record search telemetry", t)
                    } finally {
                        redirectChains.remove(action.sessionId)
                    }
                }
            }
            is DownloadAction.AddDownloadAction -> {
                metrics.track(Event.DownloadAdded)
            }
        }

        next(action)

        // Post process actions
        when (action) {
            is TabListAction.AddTabAction,
            is TabListAction.AddMultipleTabsAction,
            is TabListAction.RemoveTabAction,
            is TabListAction.RemoveAllNormalTabsAction,
            is TabListAction.RemoveAllTabsAction,
            is TabListAction.RestoreAction -> {
                // Update/Persist tabs count whenever it changes
                settings.openTabsCount = context.state.normalTabs.count()
            }
        }
    }
}
