package ru.yandex.direct.web.entity.uac.service

import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Service
import ru.yandex.direct.core.entity.banner.type.href.BannerUrlCheckService
import ru.yandex.direct.core.entity.feature.service.FeatureService
import ru.yandex.direct.core.entity.feature.service.enabled
import ru.yandex.direct.core.entity.mobilecontent.util.MobileAppStoreUrlParser
import ru.yandex.direct.core.entity.mobilecontent.util.MobileAppStoreUrlParser.GOOGLE_PLAY_DOMAIN
import ru.yandex.direct.core.entity.mobilecontent.util.MobileAppStoreUrlParser.ITUNES_APP_STORE_DOMAIN
import ru.yandex.direct.core.entity.mobilecontent.util.MobileAppStoreUrlParser.ITUNES_APP_STORE_OLD_DOMAIN
import ru.yandex.direct.core.entity.trustedredirects.service.TrustedRedirectsService
import ru.yandex.direct.core.entity.uac.model.AppInfo
import ru.yandex.direct.core.entity.uac.model.Platform
import ru.yandex.direct.core.entity.uac.model.TrackingUrl
import ru.yandex.direct.core.entity.uac.repository.ydb.UacYdbAppInfoRepository
import ru.yandex.direct.core.entity.uac.repository.ydb.model.UacYdbAppInfo
import ru.yandex.direct.core.entity.uac.service.UacAppInfoService
import ru.yandex.direct.core.entity.uac.service.trackingurl.ParserType
import ru.yandex.direct.core.entity.uac.service.trackingurl.TrackingUrlParseService
import ru.yandex.direct.core.entity.uac.validation.trackingUrlWrongRedirect
import ru.yandex.direct.core.entity.zora.ZoraService
import ru.yandex.direct.core.service.urlchecker.RedirectChecker.extractLocation
import ru.yandex.direct.feature.FeatureName
import ru.yandex.direct.integrations.configuration.IntegrationsConfiguration
import ru.yandex.direct.rotor.client.RotorClient
import ru.yandex.direct.validation.result.Defect
import ru.yandex.direct.web.core.security.DirectWebAuthenticationSource

@Service
class UacMobileAppService(
    private val uacAppInfoService: UacAppInfoService,
    private val trackingUrlParseService: TrackingUrlParseService,
    private val bannerUrlCheckService: BannerUrlCheckService,
    private val uacYdbAppInfoRepository: UacYdbAppInfoRepository,
    private val trustedRedirectsService: TrustedRedirectsService,
    private val zoraService: ZoraService,
    @Qualifier(IntegrationsConfiguration.ROTOR_TRACKING_URL_ANDROID_CLIENT)
    private val rotorTrackingUrlAndroidClient: RotorClient,
    @Qualifier(IntegrationsConfiguration.ROTOR_TRACKING_URL_IOS_CLIENT)
    private val rotorTrackingUrlIosClient: RotorClient,
    private val featureService: FeatureService,
    private val authenticationSource: DirectWebAuthenticationSource,
) {

    companion object {
        private val USER_AGENT_BY_STORE = mapOf(
            Platform.ANDROID to "Dalvik/2.1.0 (Linux; U; Android 8.1.0; DUA-L22 Build/HONORDUA-L22)",
            Platform.IOS to "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko)" +
                " Version/14.0.3 Mobile/15E148 Safari/604.1",
        )
        private val IOS_APP_ID_REGEX = "id[0-9]+".toRegex()
        private val STORE_DOMAINS = setOf(
            GOOGLE_PLAY_DOMAIN,
            ITUNES_APP_STORE_OLD_DOMAIN,
            ITUNES_APP_STORE_DOMAIN,
        )
    }

    fun getAppInfo(appId: String?): AppInfo? = appId
        ?.let { getAppInfos(listOf(it)) }
        ?.firstOrNull()

    fun getAppInfos(appIds: Collection<String>): List<AppInfo> {
        return uacYdbAppInfoRepository.getAppInfoByIds(appIds)
            .map { uacAppInfoService.getAppInfo(it) }
    }

    fun getTrackingUrl(trackingUrl: String?, appInfo: AppInfo?): TrackingUrl? = trackingUrl
        ?.let { trackingUrlParseService.parse(it, appInfo?.platform, ParserType.TRACKING_URL) }

    fun getImpressionUrl(impressionUrl: String?, appInfo: AppInfo?): TrackingUrl? = impressionUrl
        ?.let { trackingUrlParseService.parse(it, appInfo?.platform, ParserType.IMPRESSION_URL) }

    fun getAppInfoByBundleIds(bundleIds: Collection<String>): List<UacYdbAppInfo> {
        return uacYdbAppInfoRepository.getAppInfoByBundleIds(bundleIds)
    }

    fun validateTrackingUrl(trackingUrl: String?, appInfo: AppInfo?): Defect<Void>? = validateTrackingUrl(
        trackingUrl,
        appInfo?.let { AppValidationInfo(platform = appInfo.platform, appId = appInfo.appId) }
    )

    fun validateTrackingUrl(trackingUrl: String?, appValidationInfo: AppValidationInfo?): Defect<Void>? {
        return trackingUrlParseService.validateTracking(trackingUrl) ?: validateAppRedirect(
            trackingUrl, appValidationInfo
        )
    }

    fun validateImpressionUrl(impressionUrl: String?): Defect<Void>? {
        return trackingUrlParseService.validateImpression(impressionUrl)
    }

    fun validateRedirectUrl(trackingUrl: String?, appValidationInfo: AppValidationInfo?): Defect<Void>? {
        if (trackingUrlParseService.validateTracking(trackingUrl) != null) {
            val defect = validateTrustedRedirects(trackingUrl)
            if (defect != null) {
                return defect
            }
        }
        return validateAppRedirect(trackingUrl, appValidationInfo)
    }

    private fun validateTrustedRedirects(trackingUrl: String?): Defect<Void>? {
        if (trackingUrl.isNullOrBlank()) {
            return null
        }
        if (trustedRedirectsService.checkTrackingHref(trackingUrl) != TrustedRedirectsService.Result.TRUSTED) {
            return trackingUrlWrongRedirect()
        }
        return null
    }

    private fun isValidateGorotorFeatureEnabled(): Boolean {
        val uid = authenticationSource.authentication.operator.uid
        return featureService.isEnabled(uid, FeatureName.VALIDATE_TRACKING_URLS_GOROTOR)
    }

    private fun validateAppRedirect(trackingUrl: String?, appValidationInfo: AppValidationInfo?): Defect<Void>? {
        if (trackingUrl.isNullOrBlank() || appValidationInfo == null
            || FeatureName.IGNORE_TRACKING_URL_APP_VALIDATION.enabled()
        ) {
            return null
        }

        val appId = appValidationInfo.appId
        val userAgent = USER_AGENT_BY_STORE[appValidationInfo.platform]

        val redirectResult = if (doValidateTrackingUrl(trackingUrl, appId, userAgent) == null) {
            null
        } else if (doValidateTrackingUrl(trackingUrl, appId) == null) {
            null
        } else {
            doValidateTrackingUrlInstantly(trackingUrl, appId, userAgent)
        }

        return if (redirectResult != null && isValidateGorotorFeatureEnabled()) {
            doValidateTrackingUrlRotor(trackingUrl, appId, appValidationInfo.platform)
        } else {
            redirectResult
        }
    }

    fun doValidateTrackingUrlRotor(trackingUrl: String, platform: Platform): String? {
        val client = when (platform) {
            Platform.ANDROID -> rotorTrackingUrlAndroidClient
            Platform.IOS -> rotorTrackingUrlIosClient
            else -> null
        }
        val result = client?.get(deleteBadMacros(trackingUrl))
        if (result?.trace?.logEvents != null) {
            for (logEvent in result.trace!!.logEvents!!) {
                val urlHost = getUrlHost(logEvent?.jobResource?.url)
                if (urlHost in STORE_DOMAINS) {
                    return logEvent.jobResource!!.url
                }
            }
        }
        return result?.resultUrl
    }

    private fun doValidateTrackingUrlRotor(trackingUrl: String, appId: String, platform: Platform): Defect<Void>? {
        val resultUrl = doValidateTrackingUrlRotor(trackingUrl, platform)
        return validateRedirectUrl(resultUrl, appId)
    }

    private fun doValidateTrackingUrl(trackingUrl: String, appId: String, userAgent: String? = null): Defect<Void>? {
        val redirectResult = bannerUrlCheckService.getRedirect(deleteBadMacros(trackingUrl), userAgent, true)
        if (!redirectResult.isSuccessful) {
            return trackingUrlWrongRedirect()
        }

        return validateRedirectUrl(redirectResult.redirectUrl, appId)
    }

    private fun doValidateTrackingUrlInstantly(trackingUrl: String, appId: String, userAgent: String?): Defect<Void>? {
        val zoraResult = zoraService.fetchByUrl(deleteBadMacros(trackingUrl), true, userAgent)

        if (zoraResult.errors != null) {
            return trackingUrlWrongRedirect()
        }
        val successResult = zoraResult.success
        if (!successResult.isRedirect) {
            return trackingUrlWrongRedirect()
        }
        val redirectUrl = extractLocation(trackingUrl, successResult)

        return validateRedirectUrl(redirectUrl, appId)
    }

    private fun deleteBadMacros(trackingUrl: String): String {
        return trackingUrl.replace(Regex("=(\\{(.*?)})(_?(\\{(.*?)}))*"), "=")
    }

    private fun validateRedirectUrl(redirectUrl: String?, appId: String): Defect<Void>? {
        val storeUrlOptional = MobileAppStoreUrlParser.parse(redirectUrl)
        if (storeUrlOptional.isEmpty) {
            return trackingUrlWrongRedirect()
        }

        return if (storeUrlOptional.get().storeContentId == appId) {
            null
        } else {
            trackingUrlWrongRedirect()
        }
    }

    fun getAppValidationInfoByAppInfoId(appInfoId: String): AppValidationInfo? {
        return uacYdbAppInfoRepository.getAppInfoById(appInfoId)?.let {
            AppValidationInfo(it.platform, it.appId)
        }
    }

    fun getAppValidationInfoByAppId(appId: String): AppValidationInfo {
        return AppValidationInfo(
            platform = getPlatformByAppId(appId),
            appId = appId,
        )
    }

    private fun getPlatformByAppId(appId: String) = when (IOS_APP_ID_REGEX.matches(appId)) {
        true -> Platform.IOS
        false -> Platform.ANDROID
    }

    private fun getUrlHost(url: String?): String? {
        if (url.isNullOrBlank()) {
            return ""
        }
        val maybeUri = MobileAppStoreUrlParser.sanitizeUrl(url)
        if (!maybeUri.isPresent) {
            return ""
        }
        return maybeUri.get().host
    }
}

data class AppValidationInfo(
    val platform: Platform,
    val appId: String,
)
