//
//  DataManager+Bookmarks.swift
//  Yattee
//
//  Bookmark operations for DataManager.
//

import Foundation
import SwiftData

extension DataManager {
    // MARK: - Bookmarks

    /// Adds a video to bookmarks.
    func addBookmark(for video: Video) {
        // Check if already bookmarked
        let videoID = video.id.videoID
        let descriptor = FetchDescriptor<Bookmark>(
            predicate: #Predicate { $0.videoID == videoID }
        )

        do {
            let existing = try modelContext.fetch(descriptor)
            guard existing.isEmpty else {
                return
            }

            // Get max sort order
            let allBookmarks = try modelContext.fetch(FetchDescriptor<Bookmark>())
            let maxOrder = allBookmarks.map(\.sortOrder).max() ?? -1

            let bookmark = Bookmark.from(video: video, sortOrder: maxOrder + 1)
            modelContext.insert(bookmark)
            save()
            
            // Update cache immediately for fast lookup
            cachedBookmarkedVideoIDs.insert(videoID)
            
            // Queue for CloudKit sync
            cloudKitSync?.queueBookmarkSave(bookmark)
            
            NotificationCenter.default.post(name: .bookmarksDidChange, object: nil)
        } catch {
            LoggingService.shared.logCloudKitError("Failed to add bookmark", error: error)
        }
    }

    /// Removes a video from bookmarks.
    func removeBookmark(for videoID: String) {
        let descriptor = FetchDescriptor<Bookmark>(
            predicate: #Predicate { $0.videoID == videoID }
        )

        do {
            let bookmarks = try modelContext.fetch(descriptor)
            guard !bookmarks.isEmpty else { return }

            // Capture scopes before deleting
            let scopes = bookmarks.map {
                SourceScope.from(
                    sourceRawValue: $0.sourceRawValue,
                    globalProvider: $0.globalProvider,
                    instanceURLString: $0.instanceURLString,
                    externalExtractor: $0.externalExtractor
                )
            }

            bookmarks.forEach { modelContext.delete($0) }
            save()

            // Update cache immediately for fast lookup
            cachedBookmarkedVideoIDs.remove(videoID)

            // Queue scoped CloudKit deletions
            for scope in scopes {
                cloudKitSync?.queueBookmarkDelete(videoID: videoID, scope: scope)
            }

            NotificationCenter.default.post(name: .bookmarksDidChange, object: nil)
        } catch {
            LoggingService.shared.logCloudKitError("Failed to remove bookmark", error: error)
        }
    }

    /// Checks if a video is bookmarked using cached Set for O(1) lookup.
    func isBookmarked(videoID: String) -> Bool {
        cachedBookmarkedVideoIDs.contains(videoID)
    }

    /// Gets all bookmarks, most recent first.
    func bookmarks(limit: Int = 100) -> [Bookmark] {
        var descriptor = FetchDescriptor<Bookmark>(
            sortBy: [SortDescriptor(\.createdAt, order: .reverse)]
        )
        descriptor.fetchLimit = limit

        do {
            return try modelContext.fetch(descriptor)
        } catch {
            LoggingService.shared.logCloudKitError("Failed to fetch bookmarks", error: error)
            return []
        }
    }

    /// Gets the total count of bookmarks.
    func bookmarksCount() -> Int {
        let descriptor = FetchDescriptor<Bookmark>()
        do {
            return try modelContext.fetchCount(descriptor)
        } catch {
            return 0
        }
    }
    
    /// Gets a bookmark for a specific video ID.
    func bookmark(for videoID: String) -> Bookmark? {
        let descriptor = FetchDescriptor<Bookmark>(
            predicate: #Predicate { $0.videoID == videoID }
        )
        return try? modelContext.fetch(descriptor).first
    }

    /// Gets all bookmarks matching a video ID. The same ID can exist
    /// under multiple source scopes; callers that care about a specific
    /// source must pick the matching entity themselves.
    func bookmarks(forVideoID videoID: String) -> [Bookmark] {
        let descriptor = FetchDescriptor<Bookmark>(
            predicate: #Predicate { $0.videoID == videoID }
        )
        return (try? modelContext.fetch(descriptor)) ?? []
    }

    /// Deletes a single bookmark without queueing a CloudKit deletion.
    /// Used by CloudKitSyncEngine when applying remote deletions.
    func deleteBookmark(_ bookmark: Bookmark) {
        let videoID = bookmark.videoID
        modelContext.delete(bookmark)
        save()

        // Keep the fast-lookup cache accurate; another scope may still
        // have a bookmark with this video ID
        if self.bookmark(for: videoID) == nil {
            cachedBookmarkedVideoIDs.remove(videoID)
        }
        TopShelfSnapshotWriter.writeBookmarks(dataManager: self)
    }

    /// Inserts a bookmark into the database.
    /// Used by CloudKitSyncEngine for applying remote bookmarks.
    func insertBookmark(_ bookmark: Bookmark) {
        // Check for duplicates within the same source scope - the same
        // video ID can legitimately exist under different sources
        let videoID = bookmark.videoID
        let descriptor = FetchDescriptor<Bookmark>(
            predicate: #Predicate { $0.videoID == videoID }
        )
        let scopeSuffix = bookmark.sourceScopeSuffix

        do {
            let existing = try modelContext.fetch(descriptor)
            if !existing.contains(where: { $0.sourceScopeSuffix == scopeSuffix }) {
                modelContext.insert(bookmark)
                save()
                cachedBookmarkedVideoIDs.insert(videoID)
            }
        } catch {
            // Insert anyway if we can't check
            modelContext.insert(bookmark)
            save()
            cachedBookmarkedVideoIDs.insert(videoID)
        }
        TopShelfSnapshotWriter.writeBookmarks(dataManager: self)
    }
    
    /// Updates bookmark tags and note for a video.
    func updateBookmark(videoID: String, tags: [String], note: String?) {
        let descriptor = FetchDescriptor<Bookmark>(
            predicate: #Predicate { $0.videoID == videoID }
        )
        
        do {
            guard let bookmark = try modelContext.fetch(descriptor).first else {
                LoggingService.shared.logCloudKitError("Failed to update bookmark: not found", error: nil)
                return
            }
            
            let now = Date()
            
            // Update tags and timestamp if changed
            if bookmark.tags != tags {
                bookmark.tags = tags
                bookmark.tagsModifiedAt = now
            }
            
            // Update note and timestamp if changed
            if bookmark.note != note {
                bookmark.note = note
                bookmark.noteModifiedAt = now
            }
            
            save()
            
            // Queue for CloudKit sync
            cloudKitSync?.queueBookmarkSave(bookmark)
            
            NotificationCenter.default.post(name: .bookmarksDidChange, object: nil)
        } catch {
            LoggingService.shared.logCloudKitError("Failed to update bookmark", error: error)
        }
    }
}

// MARK: - Source Scope

private extension Bookmark {
    /// Record-name scope suffix used to distinguish same-ID entities across sources.
    var sourceScopeSuffix: String {
        SourceScope.from(
            sourceRawValue: sourceRawValue,
            globalProvider: globalProvider,
            instanceURLString: instanceURLString,
            externalExtractor: externalExtractor
        ).recordNameSuffix
    }
}
