package com.ustadmobile.core.db.dao.xapi

import com.ustadmobile.door.DoorDbType
import com.ustadmobile.door.EntityInsertionAdapter
import com.ustadmobile.door.PreparedStatementConfig
import com.ustadmobile.door.ext.createArrayOrProxyArrayOf
import com.ustadmobile.door.ext.prepareAndUseStatementAsync
import com.ustadmobile.door.jdbc.PreparedStatement
import com.ustadmobile.door.jdbc.ext.executeQueryAsyncKmp
import com.ustadmobile.door.jdbc.ext.executeUpdateAsyncKmp
import com.ustadmobile.door.jdbc.ext.mapNextRow
import com.ustadmobile.door.jdbc.ext.mapRows
import com.ustadmobile.door.jdbc.ext.useResults
import com.ustadmobile.door.room.RoomDatabase
import com.ustadmobile.lib.db.composites.ActorUidEtagAndLastMod
import com.ustadmobile.lib.db.entities.xapi.ActorEntity
import kotlin.Boolean
import kotlin.IllegalArgumentException
import kotlin.Long
import kotlin.String
import kotlin.collections.List

public class ActorDao_JdbcImpl(
  public val _db: RoomDatabase,
) : ActorDao() {
  public val _insertAdapterActorEntity_ignore: EntityInsertionAdapter<ActorEntity> = object :
      EntityInsertionAdapter<ActorEntity>(_db) {
    override fun makeSql(returnsId: Boolean): String = when(dbType) {
      DoorDbType.SQLITE -> {
        "INSERT  OR IGNORE INTO ActorEntity (actorUid, actorPersonUid, actorName, actorMbox, actorMbox_sha1sum, actorOpenid, actorAccountName, actorAccountHomePage, actorEtag, actorLct, actorObjectType) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
      }
      DoorDbType.POSTGRES ->  {
        "INSERT INTO ActorEntity (actorUid, actorPersonUid, actorName, actorMbox, actorMbox_sha1sum, actorOpenid, actorAccountName, actorAccountHomePage, actorEtag, actorLct, actorObjectType) VALUES(COALESCE(?,nextval('ActorEntity_actorUid_seq')), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (actorUid) DO NOTHING" + if(returnsId) { " RETURNING actorUid" } else "" 
      }
      else -> {
        throw IllegalArgumentException("Unsupported db type")
      }
    }

    override fun bindPreparedStmtToEntity(stmt: PreparedStatement, entity: ActorEntity) {
      if(entity.actorUid == 0L) {
        stmt.setObject(1, null)
      } else {
        stmt.setLong(1, entity.actorUid)
      }
      stmt.setLong(2, entity.actorPersonUid)
      stmt.setString(3, entity.actorName)
      stmt.setString(4, entity.actorMbox)
      stmt.setString(5, entity.actorMbox_sha1sum)
      stmt.setString(6, entity.actorOpenid)
      stmt.setString(7, entity.actorAccountName)
      stmt.setString(8, entity.actorAccountHomePage)
      stmt.setLong(9, entity.actorEtag)
      stmt.setLong(10, entity.actorLct)
      stmt.setInt(11, entity.actorObjectType)
    }
  }

  public val _insertAdapterActorEntity_upsert: EntityInsertionAdapter<ActorEntity> = object :
      EntityInsertionAdapter<ActorEntity>(_db) {
    override fun makeSql(returnsId: Boolean): String = when(dbType) {
      DoorDbType.SQLITE -> {
        "INSERT OR REPLACE INTO ActorEntity (actorUid, actorPersonUid, actorName, actorMbox, actorMbox_sha1sum, actorOpenid, actorAccountName, actorAccountHomePage, actorEtag, actorLct, actorObjectType) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
      }
      DoorDbType.POSTGRES ->  {
        "INSERT INTO ActorEntity (actorUid, actorPersonUid, actorName, actorMbox, actorMbox_sha1sum, actorOpenid, actorAccountName, actorAccountHomePage, actorEtag, actorLct, actorObjectType) VALUES(COALESCE(?,nextval('ActorEntity_actorUid_seq')), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (actorUid) DO UPDATE SET actorPersonUid = excluded.actorPersonUid,actorName = excluded.actorName,actorMbox = excluded.actorMbox,actorMbox_sha1sum = excluded.actorMbox_sha1sum,actorOpenid = excluded.actorOpenid,actorAccountName = excluded.actorAccountName,actorAccountHomePage = excluded.actorAccountHomePage,actorEtag = excluded.actorEtag,actorLct = excluded.actorLct,actorObjectType = excluded.actorObjectType" + if(returnsId) { " RETURNING actorUid" } else "" 
      }
      else -> {
        throw IllegalArgumentException("Unsupported db type")
      }
    }

    override fun bindPreparedStmtToEntity(stmt: PreparedStatement, entity: ActorEntity) {
      if(entity.actorUid == 0L) {
        stmt.setObject(1, null)
      } else {
        stmt.setLong(1, entity.actorUid)
      }
      stmt.setLong(2, entity.actorPersonUid)
      stmt.setString(3, entity.actorName)
      stmt.setString(4, entity.actorMbox)
      stmt.setString(5, entity.actorMbox_sha1sum)
      stmt.setString(6, entity.actorOpenid)
      stmt.setString(7, entity.actorAccountName)
      stmt.setString(8, entity.actorAccountHomePage)
      stmt.setLong(9, entity.actorEtag)
      stmt.setLong(10, entity.actorLct)
      stmt.setInt(11, entity.actorObjectType)
    }
  }

  override suspend fun insertOrIgnoreListAsync(entities: List<ActorEntity>) {
    _insertAdapterActorEntity_ignore.insertListAsync(entities)
  }

  override suspend fun upsertListAsync(entities: List<ActorEntity>) {
    _insertAdapterActorEntity_upsert.insertListAsync(entities)
  }

  override suspend fun updateIfNameChanged(
    uid: Long,
    name: String?,
    updateTime: Long,
  ) {
    _db.prepareAndUseStatementAsync(PreparedStatementConfig(
      sql = """
      |
      |        UPDATE ActorEntity
      |           SET actorName = ?,
      |               actorLct = ?
      |         WHERE actorUid = ?
      |           AND ActorEntity.actorName != ?
      |    
      """.trimMargin(),
      readOnly = false,)
    ) { _stmt -> 
      _stmt.setString(1,name)
      _stmt.setLong(2,updateTime)
      _stmt.setLong(3,uid)
      _stmt.setString(4,name)
      _stmt.executeUpdateAsyncKmp()
    }
  }

  override suspend fun findByUidAsync(uid: Long): ActorEntity? =
      _db.prepareAndUseStatementAsync(PreparedStatementConfig(
    sql = """
    |
    |        SELECT ActorEntity.*
    |          FROM ActorEntity
    |         WHERE ActorEntity.actorUid = ?
    |    
    """.trimMargin(),
    readOnly = true,)
  ) { _stmt -> 
    _stmt.setLong(1,uid)
    _stmt.executeQueryAsyncKmp().useResults{ _result -> 
      _result.mapNextRow(null) {
        val _tmp_actorUid = _result.getLong("actorUid")
        val _tmp_actorPersonUid = _result.getLong("actorPersonUid")
        val _tmp_actorName = _result.getString("actorName")
        val _tmp_actorMbox = _result.getString("actorMbox")
        val _tmp_actorMbox_sha1sum = _result.getString("actorMbox_sha1sum")
        val _tmp_actorOpenid = _result.getString("actorOpenid")
        val _tmp_actorAccountName = _result.getString("actorAccountName")
        val _tmp_actorAccountHomePage = _result.getString("actorAccountHomePage")
        val _tmp_actorEtag = _result.getLong("actorEtag")
        val _tmp_actorLct = _result.getLong("actorLct")
        val _tmp_actorObjectType = _result.getInt("actorObjectType")
        ActorEntity().apply {
          this.actorUid = _tmp_actorUid
          this.actorPersonUid = _tmp_actorPersonUid
          this.actorName = _tmp_actorName
          this.actorMbox = _tmp_actorMbox
          this.actorMbox_sha1sum = _tmp_actorMbox_sha1sum
          this.actorOpenid = _tmp_actorOpenid
          this.actorAccountName = _tmp_actorAccountName
          this.actorAccountHomePage = _tmp_actorAccountHomePage
          this.actorEtag = _tmp_actorEtag
          this.actorLct = _tmp_actorLct
          this.actorObjectType = _tmp_actorObjectType
        }
      }
    }
  }

  override suspend fun findByUidAndPersonUidAsync(actorUid: Long, accountPersonUid: Long):
      ActorEntity? = _db.prepareAndUseStatementAsync(PreparedStatementConfig(
    sql = """
    |
    |        SELECT ActorEntity.*
    |          FROM ActorEntity
    |         WHERE ActorEntity.actorUid = ?
    |           AND ActorEntity.actorPersonUid = ?  
    |    
    """.trimMargin(),
    readOnly = true,)
  ) { _stmt -> 
    _stmt.setLong(1,actorUid)
    _stmt.setLong(2,accountPersonUid)
    _stmt.executeQueryAsyncKmp().useResults{ _result -> 
      _result.mapNextRow(null) {
        val _tmp_actorUid = _result.getLong("actorUid")
        val _tmp_actorPersonUid = _result.getLong("actorPersonUid")
        val _tmp_actorName = _result.getString("actorName")
        val _tmp_actorMbox = _result.getString("actorMbox")
        val _tmp_actorMbox_sha1sum = _result.getString("actorMbox_sha1sum")
        val _tmp_actorOpenid = _result.getString("actorOpenid")
        val _tmp_actorAccountName = _result.getString("actorAccountName")
        val _tmp_actorAccountHomePage = _result.getString("actorAccountHomePage")
        val _tmp_actorEtag = _result.getLong("actorEtag")
        val _tmp_actorLct = _result.getLong("actorLct")
        val _tmp_actorObjectType = _result.getInt("actorObjectType")
        ActorEntity().apply {
          this.actorUid = _tmp_actorUid
          this.actorPersonUid = _tmp_actorPersonUid
          this.actorName = _tmp_actorName
          this.actorMbox = _tmp_actorMbox
          this.actorMbox_sha1sum = _tmp_actorMbox_sha1sum
          this.actorOpenid = _tmp_actorOpenid
          this.actorAccountName = _tmp_actorAccountName
          this.actorAccountHomePage = _tmp_actorAccountHomePage
          this.actorEtag = _tmp_actorEtag
          this.actorLct = _tmp_actorLct
          this.actorObjectType = _tmp_actorObjectType
        }
      }
    }
  }

  override suspend fun findUidAndEtagByListAsync(uidList: List<Long>): List<ActorUidEtagAndLastMod>
      = _db.prepareAndUseStatementAsync(PreparedStatementConfig(
    sql = """
    |
    |        SELECT ActorEntity.actorUid, ActorEntity.actorEtag, ActorEntity.actorLct
    |          FROM ActorEntity
    |         WHERE ActorEntity.actorUid IN (?)
    |    
    """.trimMargin(),
    hasListParams = true,
    readOnly = true,)
  ) { _stmt -> 
    _stmt.setArray(1, _stmt.getConnection().createArrayOrProxyArrayOf("BIGINT",
        uidList.toTypedArray()))
    _stmt.executeQueryAsyncKmp().useResults{ _result -> 
      _result.mapRows {
        val _tmp_actorUid = _result.getLong("actorUid")
        val _tmp_actorEtag = _result.getLong("actorEtag")
        val _tmp_actorLct = _result.getLong("actorLct")
        ActorUidEtagAndLastMod().apply {
          this.actorUid = _tmp_actorUid
          this.actorEtag = _tmp_actorEtag
          this.actorLct = _tmp_actorLct
        }
      }
    }
  }

  override suspend fun findGroupMembers(groupActorUid: Long): List<ActorEntity> =
      _db.prepareAndUseStatementAsync(PreparedStatementConfig(
    sql = """
    |
    |        SELECT ActorEntity.*
    |          FROM ActorEntity
    |         WHERE ActorEntity.actorUid IN (
    |               SELECT GroupMemberActorJoin.gmajMemberActorUid
    |                 FROM GroupMemberActorJoin
    |                WHERE GroupMemberActorJoin.gmajGroupActorUid = ?
    |                  AND GroupMemberActorJoin.gmajLastMod = (
    |                      SELECT GroupActorEntity.actorLct
    |                        FROM ActorEntity GroupActorEntity
    |                       WHERE GroupActorEntity.actorUid = ?)
    |              ) 
    |    
    """.trimMargin(),
    readOnly = true,)
  ) { _stmt -> 
    _stmt.setLong(1,groupActorUid)
    _stmt.setLong(2,groupActorUid)
    _stmt.executeQueryAsyncKmp().useResults{ _result -> 
      _result.mapRows {
        val _tmp_actorUid = _result.getLong("actorUid")
        val _tmp_actorPersonUid = _result.getLong("actorPersonUid")
        val _tmp_actorName = _result.getString("actorName")
        val _tmp_actorMbox = _result.getString("actorMbox")
        val _tmp_actorMbox_sha1sum = _result.getString("actorMbox_sha1sum")
        val _tmp_actorOpenid = _result.getString("actorOpenid")
        val _tmp_actorAccountName = _result.getString("actorAccountName")
        val _tmp_actorAccountHomePage = _result.getString("actorAccountHomePage")
        val _tmp_actorEtag = _result.getLong("actorEtag")
        val _tmp_actorLct = _result.getLong("actorLct")
        val _tmp_actorObjectType = _result.getInt("actorObjectType")
        ActorEntity().apply {
          this.actorUid = _tmp_actorUid
          this.actorPersonUid = _tmp_actorPersonUid
          this.actorName = _tmp_actorName
          this.actorMbox = _tmp_actorMbox
          this.actorMbox_sha1sum = _tmp_actorMbox_sha1sum
          this.actorOpenid = _tmp_actorOpenid
          this.actorAccountName = _tmp_actorAccountName
          this.actorAccountHomePage = _tmp_actorAccountHomePage
          this.actorEtag = _tmp_actorEtag
          this.actorLct = _tmp_actorLct
          this.actorObjectType = _tmp_actorObjectType
        }
      }
    }
  }
}
