package net.aliasvault.app.vaultstore.repositories

import android.util.Log
import net.aliasvault.app.utils.DateHelpers
import net.aliasvault.app.vaultstore.VaultDatabase
import net.aliasvault.app.vaultstore.models.FieldKey
import net.aliasvault.app.vaultstore.models.FieldType
import net.aliasvault.app.vaultstore.models.Item
import net.aliasvault.app.vaultstore.models.ItemField
import net.aliasvault.app.vaultstore.queries.ItemQueries
import net.aliasvault.app.vaultstore.utils.FolderUtils
import java.util.Calendar
import java.util.Date
import java.util.TimeZone
import java.util.UUID

/**
 * Repository for Item CRUD operations.
 * Handles fetching, creating, updating, and deleting items with their related data.
 */
class ItemRepository(database: VaultDatabase) : BaseRepository(database) {
    companion object {
        private const val TAG = "ItemRepository"

        private val MIN_DATE: Date = Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply {
            set(Calendar.YEAR, 1)
            set(Calendar.MONTH, Calendar.JANUARY)
            set(Calendar.DAY_OF_MONTH, 1)
            set(Calendar.HOUR_OF_DAY, 0)
            set(Calendar.MINUTE, 0)
            set(Calendar.SECOND, 0)
            set(Calendar.MILLISECOND, 0)
        }.time
    }

    // MARK: - Read Operations

    /**
     * Build folder paths for all folders.
     * Returns a map of FolderId -> path array.
     *
     * @return Map of folder ID to folder path array.
     */
    private fun buildFolderPaths(): Map<UUID, List<String>> {
        val folderPathMap = mutableMapOf<UUID, List<String>>()

        try {
            // Get all folders from database
            val folderResults = executeQuery(ItemQueries.GET_ALL_FOLDERS, emptyArray())

            if (folderResults.isEmpty()) {
                return folderPathMap
            }

            // Convert to FolderUtils.Folder format
            val folders = folderResults.mapNotNull { row ->
                try {
                    val id = UUID.fromString(row["Id"] as? String ?: return@mapNotNull null)
                    val name = row["Name"] as? String ?: return@mapNotNull null
                    val parentFolderId = (row["ParentFolderId"] as? String)?.let { UUID.fromString(it) }
                    FolderUtils.Folder(id, name, parentFolderId)
                } catch (e: Exception) {
                    Log.e(TAG, "Error parsing folder row", e)
                    null
                }
            }

            // Use shared utility to build paths for all folders
            for (folder in folders) {
                val path = FolderUtils.getFolderPath(folder.id, folders)
                if (path.isNotEmpty()) {
                    folderPathMap[folder.id] = path
                }
            }

            return folderPathMap
        } catch (e: Exception) {
            // Folders table may not exist in older vault versions
            Log.e(TAG, "Error building folder paths", e)
            return folderPathMap
        }
    }

    /**
     * Fetch all active items (not deleted, not in trash) with their fields.
     *
     * @return List of Item objects.
     */
    @Suppress("LongMethod", "NestedBlockDepth", "LoopWithTooManyJumpStatements")
    fun getAll(): List<Item> {
        val items = mutableListOf<Item>()
        val itemIds = mutableListOf<String>()

        // Build folder paths
        val folderPaths = buildFolderPaths()

        val itemResults = executeQueryWithBlobs(ItemQueries.GET_ALL_ACTIVE, emptyArray())
        for (row in itemResults) {
            try {
                val idString = row["Id"] as? String ?: continue
                val name = row["Name"] as? String
                val itemType = row["ItemType"] as? String ?: continue
                val folderId = row["FolderId"] as? String
                val logo = row["Logo"] as? ByteArray
                val hasPasskey = (row["HasPasskey"] as? Long) == 1L
                val hasAttachment = (row["HasAttachment"] as? Long) == 1L
                val hasTotp = (row["HasTotp"] as? Long) == 1L
                val createdAt = DateHelpers.parseDateString(row["CreatedAt"] as? String ?: "") ?: MIN_DATE
                val updatedAt = DateHelpers.parseDateString(row["UpdatedAt"] as? String ?: "") ?: MIN_DATE

                // Get folder path if item is in a folder
                val folderUuid = folderId?.let { UUID.fromString(it) }
                val folderPath = folderUuid?.let { folderPaths[it] }

                val item = Item(
                    id = UUID.fromString(idString),
                    name = name,
                    itemType = itemType,
                    logo = logo,
                    folderId = folderUuid,
                    folderPath = folderPath,
                    fields = emptyList(), // Will be populated below
                    hasPasskey = hasPasskey,
                    hasAttachment = hasAttachment,
                    hasTotp = hasTotp,
                    createdAt = createdAt,
                    updatedAt = updatedAt,
                )
                items.add(item)
                itemIds.add(idString)
            } catch (e: Exception) {
                Log.e(TAG, "Error parsing item row", e)
            }
        }

        // If no items, return empty list
        if (items.isEmpty()) {
            return emptyList()
        }

        // Get all field values for these items
        val fieldQuery = ItemQueries.getFieldValuesForItems(itemIds.size)

        // Build a map of itemId -> [ItemField]
        val fieldsByItemId = mutableMapOf<String, MutableList<ItemField>>()

        val fieldResults = executeQuery(fieldQuery, itemIds.toTypedArray())
        for (row in fieldResults) {
            try {
                val itemIdString = row["ItemId"] as? String ?: continue
                val fieldKey = row["FieldKey"] as? String
                val fieldDefinitionId = row["FieldDefinitionId"] as? String
                val customLabel = row["CustomLabel"] as? String
                val customFieldType = row["CustomFieldType"] as? String
                val customIsHidden = (row["CustomIsHidden"] as? Long) == 1L
                val customEnableHistory = (row["CustomEnableHistory"] as? Long) == 1L
                val value = row["Value"] as? String ?: ""
                val displayOrder = (row["DisplayOrder"] as? Long)?.toInt() ?: 0

                // Determine if this is a custom field
                val isCustomField = fieldDefinitionId != null && fieldKey == null

                // Resolve the effective field key
                val effectiveFieldKey = fieldKey ?: fieldDefinitionId ?: ""

                // Resolve field metadata
                val metadata = resolveFieldMetadata(
                    fieldKey = effectiveFieldKey,
                    customLabel = customLabel,
                    customFieldType = customFieldType,
                    customIsHidden = customIsHidden,
                    customEnableHistory = customEnableHistory,
                    isCustomField = isCustomField,
                )

                val field = ItemField(
                    fieldKey = effectiveFieldKey,
                    label = metadata.label,
                    fieldType = metadata.fieldType,
                    value = value,
                    isHidden = metadata.isHidden,
                    displayOrder = displayOrder,
                    isCustomField = isCustomField,
                    enableHistory = metadata.enableHistory,
                )

                fieldsByItemId.getOrPut(itemIdString) { mutableListOf() }.add(field)
            } catch (e: Exception) {
                Log.e(TAG, "Error parsing field row", e)
            }
        }

        // Assign fields to items
        return items.map { item ->
            val fields = fieldsByItemId[item.id.toString().uppercase()] ?: emptyList()
            item.copy(fields = fields)
        }
    }

    /**
     * Fetch a single item by ID with its fields.
     *
     * @param itemId The ID of the item to fetch.
     * @return Item object or null if not found.
     */
    fun getById(itemId: String): Item? {
        val itemResults = executeQueryWithBlobs(ItemQueries.GET_BY_ID, arrayOf(itemId.uppercase()))
        val row = itemResults.firstOrNull() ?: return null

        // Build folder paths
        val folderPaths = buildFolderPaths()

        return try {
            val idString = row["Id"] as? String ?: return null
            val name = row["Name"] as? String
            val itemType = row["ItemType"] as? String ?: return null
            val folderId = row["FolderId"] as? String
            val logo = row["Logo"] as? ByteArray
            val hasPasskey = (row["HasPasskey"] as? Long) == 1L
            val hasAttachment = (row["HasAttachment"] as? Long) == 1L
            val hasTotp = (row["HasTotp"] as? Long) == 1L
            val createdAt = DateHelpers.parseDateString(row["CreatedAt"] as? String ?: "") ?: MIN_DATE
            val updatedAt = DateHelpers.parseDateString(row["UpdatedAt"] as? String ?: "") ?: MIN_DATE

            // Get folder path if item is in a folder
            val folderUuid = folderId?.let { UUID.fromString(it) }
            val folderPath = folderUuid?.let { folderPaths[it] }

            // Get field values for this item
            val fields = mutableListOf<ItemField>()
            val fieldResults = executeQuery(ItemQueries.GET_FIELD_VALUES_FOR_ITEM, arrayOf(idString))
            for (fieldRow in fieldResults) {
                val fieldKey = fieldRow["FieldKey"] as? String
                val fieldDefinitionId = fieldRow["FieldDefinitionId"] as? String
                val customLabel = fieldRow["CustomLabel"] as? String
                val customFieldType = fieldRow["CustomFieldType"] as? String
                val customIsHidden = (fieldRow["CustomIsHidden"] as? Long) == 1L
                val customEnableHistory = (fieldRow["CustomEnableHistory"] as? Long) == 1L
                val value = fieldRow["Value"] as? String ?: ""
                val displayOrder = (fieldRow["DisplayOrder"] as? Long)?.toInt() ?: 0

                val isCustomField = fieldDefinitionId != null && fieldKey == null
                val effectiveFieldKey = fieldKey ?: fieldDefinitionId ?: ""

                val metadata = resolveFieldMetadata(
                    fieldKey = effectiveFieldKey,
                    customLabel = customLabel,
                    customFieldType = customFieldType,
                    customIsHidden = customIsHidden,
                    customEnableHistory = customEnableHistory,
                    isCustomField = isCustomField,
                )

                fields.add(
                    ItemField(
                        fieldKey = effectiveFieldKey,
                        label = metadata.label,
                        fieldType = metadata.fieldType,
                        value = value,
                        isHidden = metadata.isHidden,
                        displayOrder = displayOrder,
                        isCustomField = isCustomField,
                        enableHistory = metadata.enableHistory,
                    ),
                )
            }

            Item(
                id = UUID.fromString(idString),
                name = name,
                itemType = itemType,
                logo = logo,
                folderId = folderUuid,
                folderPath = folderPath,
                fields = fields,
                hasPasskey = hasPasskey,
                hasAttachment = hasAttachment,
                hasTotp = hasTotp,
                createdAt = createdAt,
                updatedAt = updatedAt,
            )
        } catch (e: Exception) {
            Log.e(TAG, "Error parsing item by ID", e)
            null
        }
    }

    /**
     * Fetch all unique email addresses from field values.
     *
     * @return List of email addresses.
     */
    fun getAllEmailAddresses(): List<String> {
        val results = executeQuery(ItemQueries.GET_ALL_EMAIL_ADDRESSES, arrayOf(FieldKey.LOGIN_EMAIL))
        return results.mapNotNull { it["Email"] as? String }
    }

    /**
     * Get recently deleted items (in trash).
     * Note: This returns minimal Item objects without fields for performance.
     *
     * @return List of items in trash.
     */
    @Suppress("LongMethod", "LoopWithTooManyJumpStatements")
    fun getRecentlyDeleted(): List<Item> {
        val items = mutableListOf<Item>()
        val results = executeQueryWithBlobs(ItemQueries.GET_RECENTLY_DELETED, emptyArray())

        // Build folder paths
        val folderPaths = buildFolderPaths()

        for (row in results) {
            try {
                val idString = row["Id"] as? String ?: continue
                val name = row["Name"] as? String
                val itemType = row["ItemType"] as? String ?: continue
                val folderId = row["FolderId"] as? String
                val logo = row["Logo"] as? ByteArray
                val hasPasskey = (row["HasPasskey"] as? Long) == 1L
                val hasAttachment = (row["HasAttachment"] as? Long) == 1L
                val hasTotp = (row["HasTotp"] as? Long) == 1L
                val createdAt = DateHelpers.parseDateString(row["CreatedAt"] as? String ?: "") ?: MIN_DATE
                val updatedAt = DateHelpers.parseDateString(row["UpdatedAt"] as? String ?: "") ?: MIN_DATE

                // Get folder path if item is in a folder
                val folderUuid = folderId?.let { UUID.fromString(it) }
                val folderPath = folderUuid?.let { folderPaths[it] }

                items.add(
                    Item(
                        id = UUID.fromString(idString),
                        name = name,
                        itemType = itemType,
                        logo = logo,
                        folderId = folderUuid,
                        folderPath = folderPath,
                        fields = emptyList(), // Not loading fields for trash items
                        hasPasskey = hasPasskey,
                        hasAttachment = hasAttachment,
                        hasTotp = hasTotp,
                        createdAt = createdAt,
                        updatedAt = updatedAt,
                    ),
                )
            } catch (e: Exception) {
                Log.e(TAG, "Error parsing recently deleted item row", e)
            }
        }

        return items
    }

    /**
     * Get the database version from the __EFMigrationsHistory table.
     *
     * @return Database version string (e.g., "1.0.0").
     */
    fun getDatabaseVersion(): String {
        val results = executeQuery(ItemQueries.GET_DATABASE_VERSION, emptyArray())

        if (results.isEmpty()) {
            Log.d(TAG, "No migrations found in database, returning default version")
            return "0.0.0"
        }

        val migrationId = results[0]["MigrationId"] as? String
        if (migrationId == null) {
            return "0.0.0"
        }

        val versionRegex = Regex("_(\\d+\\.\\d+\\.\\d+)-")
        val match = versionRegex.find(migrationId)

        return if (match != null && match.groupValues.size > 1) {
            match.groupValues[1]
        } else {
            Log.d(TAG, "Could not extract version from migration ID '$migrationId', returning default")
            "0.0.0"
        }
    }

    /**
     * Get count of items in trash.
     *
     * @return Number of items in trash.
     */
    fun getRecentlyDeletedCount(): Int {
        val results = executeQuery(ItemQueries.COUNT_RECENTLY_DELETED, emptyArray())
        return (results.firstOrNull()?.get("count") as? Long)?.toInt() ?: 0
    }

    /**
     * Get the first non-deleted TOTP secret for an item, or null when there is none.
     * Used by the autofill service to copy the current TOTP code to the clipboard
     * when the user selects a credential to fill.
     *
     * @param itemId The UUID of the item.
     * @return The Base32 secret key string, or null.
     */
    fun getTotpSecretForItem(itemId: String): String? {
        val results = executeQuery(
            "SELECT SecretKey FROM TotpCodes WHERE ItemId = ? AND IsDeleted = 0 ORDER BY Name ASC LIMIT 1",
            arrayOf(itemId.uppercase()),
        )
        return results.firstOrNull()?.get("SecretKey") as? String
    }

    // MARK: - Write Operations

    /**
     * Move an item to trash (set DeletedAt timestamp).
     *
     * @param itemId The ID of the item to trash.
     * @return Number of rows affected.
     */
    fun trash(itemId: String): Int {
        return withTransaction {
            val now = now()
            executeUpdate(ItemQueries.TRASH_ITEM, arrayOf(now, now, itemId))
        }
    }

    /**
     * Restore an item from trash (clear DeletedAt).
     *
     * @param itemId The ID of the item to restore.
     * @return Number of rows affected.
     */
    fun restore(itemId: String): Int {
        return withTransaction {
            val now = now()
            executeUpdate(ItemQueries.RESTORE_ITEM, arrayOf(now, itemId))
        }
    }

    /**
     * Permanently delete an item (tombstone).
     * Converts item to tombstone and soft deletes all related data.
     *
     * @param itemId The ID of the item to permanently delete.
     * @return Number of rows affected.
     */
    fun permanentlyDelete(itemId: String): Int {
        return withTransaction {
            val now = now()

            // Soft delete related FieldValues
            softDeleteByForeignKey("FieldValues", "ItemId", itemId)

            // Soft delete related data
            softDeleteByForeignKey("TotpCodes", "ItemId", itemId)

            // Soft delete attachments AND zero their blob bytes — tombstone stays
            // for sync but storage is reclaimed immediately.
            executeUpdate(
                "UPDATE Attachments SET IsDeleted = 1, Blob = X'', UpdatedAt = ? WHERE ItemId = ? AND IsDeleted = 0",
                arrayOf(now, itemId),
            )

            softDeleteByForeignKey("Passkeys", "ItemId", itemId)

            if (tableExists("ItemTags")) {
                softDeleteByForeignKey("ItemTags", "ItemId", itemId)
            }
            if (tableExists("FieldHistories")) {
                softDeleteByForeignKey("FieldHistories", "ItemId", itemId)
            }

            // Convert item to tombstone
            executeUpdate(ItemQueries.TOMBSTONE_ITEM, arrayOf(now, itemId))
        }
    }

    /**
     * Create a new item with its fields.
     *
     * @param item The item to create.
     * @return The ID of the created item.
     */
    @Suppress("UNUSED_PARAMETER") // Method under construction, will be implemented
    fun create(item: Item): String {
        error("Create operations should use VaultMutate - repository pattern under construction")
    }

    /**
     * Update an existing item with its fields.
     *
     * @param item The item to update.
     * @return Number of rows affected.
     */
    @Suppress("UNUSED_PARAMETER") // Method under construction, will be implemented
    fun update(item: Item): Int {
        error("Update operations should use VaultMutate - repository pattern under construction")
    }

    // MARK: - Helper Methods

    /**
     * Helper class to hold resolved field metadata.
     */
    private data class FieldMetadata(
        val label: String,
        val fieldType: String,
        val isHidden: Boolean,
        val enableHistory: Boolean,
    )

    /**
     * Resolve field metadata for system fields and custom fields.
     */
    @Suppress("CyclomaticComplexMethod", "LongParameterList")
    // LongParameterList suppressed: All parameters are needed to determine field metadata
    private fun resolveFieldMetadata(
        fieldKey: String,
        customLabel: String?,
        customFieldType: String?,
        customIsHidden: Boolean,
        customEnableHistory: Boolean,
        isCustomField: Boolean,
    ): FieldMetadata {
        if (isCustomField) {
            return FieldMetadata(
                label = customLabel ?: fieldKey,
                fieldType = customFieldType ?: FieldType.TEXT,
                isHidden = customIsHidden,
                enableHistory = customEnableHistory,
            )
        }

        // System field metadata based on FieldKey constants
        return when (fieldKey) {
            FieldKey.LOGIN_USERNAME -> FieldMetadata("Username", FieldType.TEXT, false, false)
            FieldKey.LOGIN_PASSWORD -> FieldMetadata("Password", FieldType.PASSWORD, true, true)
            FieldKey.LOGIN_EMAIL -> FieldMetadata("Email", FieldType.EMAIL, false, false)
            FieldKey.LOGIN_URL -> FieldMetadata("URL", FieldType.U_R_L, false, false)
            FieldKey.CARD_NUMBER -> FieldMetadata("Card Number", FieldType.TEXT, true, false)
            FieldKey.CARD_CARDHOLDER_NAME -> FieldMetadata("Cardholder Name", FieldType.TEXT, false, false)
            FieldKey.CARD_EXPIRY_MONTH -> FieldMetadata("Expiry Month", FieldType.TEXT, false, false)
            FieldKey.CARD_EXPIRY_YEAR -> FieldMetadata("Expiry Year", FieldType.TEXT, false, false)
            FieldKey.CARD_CVV -> FieldMetadata("CVV", FieldType.PASSWORD, true, false)
            FieldKey.CARD_PIN -> FieldMetadata("PIN", FieldType.PASSWORD, true, false)
            FieldKey.ALIAS_FIRST_NAME -> FieldMetadata("First Name", FieldType.TEXT, false, false)
            FieldKey.ALIAS_LAST_NAME -> FieldMetadata("Last Name", FieldType.TEXT, false, false)
            FieldKey.ALIAS_GENDER -> FieldMetadata("Gender", FieldType.TEXT, false, false)
            FieldKey.ALIAS_BIRTHDATE -> FieldMetadata("Birth Date", FieldType.DATE, false, false)
            FieldKey.NOTES_CONTENT -> FieldMetadata("Notes", FieldType.TEXT_AREA, false, false)
            else -> FieldMetadata(fieldKey, FieldType.TEXT, false, false)
        }
    }

    /**
     * Execute a SELECT query that may return BLOB columns.
     * Unlike executeQuery which converts all params to strings, this preserves ByteArray types.
     */
    private fun executeQueryWithBlobs(query: String, params: Array<Any?>): List<Map<String, Any?>> {
        val db = database.dbConnection ?: error("Database not initialized")
        val cursor = db.query(query, params.map { it?.toString() }.toTypedArray())

        val results = mutableListOf<Map<String, Any?>>()
        cursor.use {
            val columnNames = it.columnNames
            while (it.moveToNext()) {
                val row = mutableMapOf<String, Any?>()
                for (columnName in columnNames) {
                    when (it.getType(it.getColumnIndexOrThrow(columnName))) {
                        android.database.Cursor.FIELD_TYPE_NULL -> row[columnName] = null
                        android.database.Cursor.FIELD_TYPE_INTEGER -> row[columnName] = it.getLong(
                            it.getColumnIndexOrThrow(columnName),
                        )
                        android.database.Cursor.FIELD_TYPE_FLOAT -> row[columnName] = it.getDouble(
                            it.getColumnIndexOrThrow(columnName),
                        )
                        android.database.Cursor.FIELD_TYPE_STRING -> row[columnName] = it.getString(
                            it.getColumnIndexOrThrow(columnName),
                        )
                        android.database.Cursor.FIELD_TYPE_BLOB -> row[columnName] = it.getBlob(
                            it.getColumnIndexOrThrow(columnName),
                        )
                    }
                }
                results.add(row)
            }
        }

        return results
    }
}
