/* 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 android.content.Context
import mozilla.components.browser.errorpages.ErrorPages
import mozilla.components.browser.errorpages.ErrorType
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.request.RequestInterceptor
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.exceptions.ExceptionDomains
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.tryGetHostFromUrl

class AppRequestInterceptor(private val context: Context) : RequestInterceptor {
    override fun onLoadRequest(session: EngineSession, uri: String): RequestInterceptor.InterceptionResponse? {
        val host = uri.tryGetHostFromUrl()

        adjustTrackingProtection(host, context, session)

        // WebChannel-driven authentication does not require a separate redirect interceptor.
        return if (context.isInExperiment(Experiments.asFeatureWebChannelsDisabled)) {
            context.components.services.accountsAuthFeature.interceptor.onLoadRequest(session, uri)
        } else {
            null
        }
    }

    private fun adjustTrackingProtection(host: String, context: Context, session: EngineSession) {
        val trackingProtectionException = ExceptionDomains(context).load().contains(host)
        val trackingProtectionEnabled = context.settings().shouldUseTrackingProtection
        if (trackingProtectionException || !trackingProtectionEnabled) {
            session.disableTrackingProtection()
        } else {
            val core = context.components.core
            val policy = core.createTrackingProtectionPolicy(normalMode = true)
            core.engine.settings.trackingProtectionPolicy = policy
            session.enableTrackingProtection(policy)
        }
    }

    override fun onErrorRequest(
        session: EngineSession,
        errorType: ErrorType,
        uri: String?
    ): RequestInterceptor.ErrorResponse? {
        val riskLevel = getRiskLevel(errorType)
        val htmlResource = getPageForRiskLevel(riskLevel)
        val cssResource = getStyleForRiskLevel(riskLevel)

        context.components.analytics.metrics.track(Event.ErrorPageVisited(errorType))
        return RequestInterceptor.ErrorResponse(
            ErrorPages
                .createErrorPage(context, errorType, uri = uri, htmlResource = htmlResource, cssResource = cssResource)
        )
    }

    private fun getPageForRiskLevel(riskLevel: RiskLevel): Int {
        return when (riskLevel) {
            RiskLevel.Low -> R.raw.low_risk_error_pages
            RiskLevel.Medium -> R.raw.medium_and_high_risk_error_pages
            RiskLevel.High -> R.raw.medium_and_high_risk_error_pages
        }
    }

    private fun getStyleForRiskLevel(riskLevel: RiskLevel): Int {
        return when (riskLevel) {
            RiskLevel.Low -> R.raw.low_and_medium_risk_error_style
            RiskLevel.Medium -> R.raw.low_and_medium_risk_error_style
            RiskLevel.High -> R.raw.high_risk_error_style
        }
    }

    private fun getRiskLevel(errorType: ErrorType): RiskLevel {
        return when (errorType) {
            // Low risk errors
            ErrorType.UNKNOWN -> RiskLevel.Low
            ErrorType.ERROR_NET_INTERRUPT -> RiskLevel.Low
            ErrorType.ERROR_NET_TIMEOUT -> RiskLevel.Low
            ErrorType.ERROR_CONNECTION_REFUSED -> RiskLevel.Low
            ErrorType.ERROR_UNKNOWN_SOCKET_TYPE -> RiskLevel.Low
            ErrorType.ERROR_REDIRECT_LOOP -> RiskLevel.Low
            ErrorType.ERROR_OFFLINE -> RiskLevel.Low
            ErrorType.ERROR_NET_RESET -> RiskLevel.Low
            ErrorType.ERROR_UNSAFE_CONTENT_TYPE -> RiskLevel.Low
            ErrorType.ERROR_CORRUPTED_CONTENT -> RiskLevel.Low
            ErrorType.ERROR_CONTENT_CRASHED -> RiskLevel.Low
            ErrorType.ERROR_INVALID_CONTENT_ENCODING -> RiskLevel.Low
            ErrorType.ERROR_UNKNOWN_HOST -> RiskLevel.Low
            ErrorType.ERROR_MALFORMED_URI -> RiskLevel.Low
            ErrorType.ERROR_FILE_NOT_FOUND -> RiskLevel.Low
            ErrorType.ERROR_FILE_ACCESS_DENIED -> RiskLevel.Low
            ErrorType.ERROR_PROXY_CONNECTION_REFUSED -> RiskLevel.Low
            ErrorType.ERROR_UNKNOWN_PROXY_HOST -> RiskLevel.Low
            ErrorType.ERROR_UNKNOWN_PROTOCOL -> RiskLevel.Low

            // Medium risk errors
            ErrorType.ERROR_SECURITY_BAD_CERT -> RiskLevel.Medium
            ErrorType.ERROR_SECURITY_SSL -> RiskLevel.Medium
            ErrorType.ERROR_PORT_BLOCKED -> RiskLevel.Medium

            // High risk errors
            ErrorType.ERROR_SAFEBROWSING_HARMFUL_URI -> RiskLevel.High
            ErrorType.ERROR_SAFEBROWSING_MALWARE_URI -> RiskLevel.High
            ErrorType.ERROR_SAFEBROWSING_PHISHING_URI -> RiskLevel.High
            ErrorType.ERROR_SAFEBROWSING_UNWANTED_URI -> RiskLevel.High
        }
    }

    sealed class RiskLevel {
        object Low : RiskLevel()
        object Medium : RiskLevel()
        object High : RiskLevel()
    }
}
