package ru.yandex.direct.infrastructure.mysql.repositories

import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.andWhere
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import ru.yandex.direct.domain.retargeting.AdGroupIDsCriteria
import ru.yandex.direct.domain.retargeting.CampaignIDsCriteria
import ru.yandex.direct.domain.retargeting.IDsCriteria
import ru.yandex.direct.domain.retargeting.InterestIDsCriteria
import ru.yandex.direct.domain.retargeting.Retargeting
import ru.yandex.direct.domain.retargeting.RetargetingConditionIDsCriteria
import ru.yandex.direct.domain.retargeting.RetargetingConditionRepository
import ru.yandex.direct.domain.retargeting.RetargetingID
import ru.yandex.direct.domain.retargeting.RetargetingRepository
import ru.yandex.direct.domain.retargeting.SelectionCriteria
import ru.yandex.direct.domain.retargeting.SuspendedCriteria
import ru.yandex.direct.infrastructure.mysql.sharding.ShardedDatabase
import ru.yandex.direct.infrastructure.mysql.tables.BidsRetargeting
import ru.yandex.direct.infrastructure.mysql.tables.Campaigns
import ru.yandex.direct.infrastructure.mysql.tables.YesNo

class MysqlRetargetingRepository(
    private val db: ShardedDatabase,

    /**
     * shard used for findBySelection (and other methods) that implies, that all data fetched from single shard.
     */
    private val shard: Database,

    private val retargetingConditionRepository: RetargetingConditionRepository
) : RetargetingRepository {
    fun withShard(shard: Database) = MysqlRetargetingRepository(
        db = db,
        shard = shard,
        retargetingConditionRepository = retargetingConditionRepository
    )

    override fun findBySelection(selection: SelectionCriteria): Collection<Retargeting> {
        val retargetings = transaction(shard) {
            val query = BidsRetargeting.slice(BidsRetargeting.id).selectAll()

            selection.forEach { criteria ->
                when (criteria) {
                    is IDsCriteria -> query.andWhere { BidsRetargeting.id.inList(criteria.ids) }
                    is AdGroupIDsCriteria -> query.andWhere { BidsRetargeting.adGroupId.inList(criteria.ids) }
                    is CampaignIDsCriteria -> query.andWhere { BidsRetargeting.campaignId.inList(criteria.ids) }
                    is RetargetingConditionIDsCriteria -> query.andWhere { BidsRetargeting.retCondId.inList(criteria.ids) }
                    is SuspendedCriteria -> query.andWhere { BidsRetargeting.isSuspended.eq(criteria.suspended) }
                    // TODO: This can be done through JSON_EXTRACT usage
                    is InterestIDsCriteria -> { /* Interests will be filtered after we read retargetings */
                    }
                }
            }

            query.mapNotNull { findById(it[BidsRetargeting.id]) }
        }

        return selection.apply(retargetings)
    }

    override fun findById(id: RetargetingID): Retargeting? {
        val row = transaction(shard) {
            (BidsRetargeting innerJoin Campaigns).select {
                BidsRetargeting.id.eq(id) and
                    Campaigns.statusEmpty.eq(YesNo.NO)
            }.singleOrNull()
        } ?: return null

        val retargetingCondition =
            retargetingConditionRepository.findById(row[BidsRetargeting.retCondId]) ?: return null

        return Retargeting(
            id = row[BidsRetargeting.id],
            adGroupID = row[BidsRetargeting.adGroupId],
            campaignID = row[BidsRetargeting.campaignId],
            condition = retargetingCondition,
            lastChangeTime = row[BidsRetargeting.modTime],
            suspended = row[BidsRetargeting.isSuspended],
            priceContext = row[BidsRetargeting.priceContext],
        )
    }

    override fun create(retargeting: Retargeting): RetargetingID? {
        val retargetingId = db.generateRetargetingId() ?: return null

        transaction(shard) {
            BidsRetargeting.insert {
                it[id] = retargetingId
                it[adGroupId] = retargeting.adGroupID
                it[campaignId] = retargeting.campaignID
                it[retCondId] = retargeting.condition.id
                it[isSuspended] = retargeting.suspended
                it[priceContext] = retargeting.priceContext
            }
        }

        return retargetingId
    }
}
