import Foundation
import SwiftData

@MainActor
final class CourseStore {
    let modelContext: ModelContext

    init(modelContext: ModelContext) {
        self.modelContext = modelContext
    }

    func settings() -> AppSettings {
        let descriptor = FetchDescriptor<AppSettings>()
        if let existing = try? modelContext.fetch(descriptor).first {
            return existing
        }
        let created = AppSettings()
        modelContext.insert(created)
        try? modelContext.save()
        return created
    }

    func migrateIfNeeded() {
        let defaults = UserDefaults.standard
        let version = defaults.integer(forKey: CourseLibrary.schemaMigrationKey)
        if version >= CourseLibrary.currentSchemaVersion {
            try? CourseLibrary.ensureCoursesRoot()
            return
        }
        // Child-deck reset: drop legacy library catalog/albums from runtime use.
        // Keep StudySession rows that already have courseId; purge orphan catalog noise.
        if let catalog = try? modelContext.fetch(FetchDescriptor<CatalogEntry>()) {
            for entry in catalog { modelContext.delete(entry) }
        }
        if let albums = try? modelContext.fetch(FetchDescriptor<Album>()) {
            for album in albums { modelContext.delete(album) }
        }
        try? CourseLibrary.ensureCoursesRoot()
        // Clear old library folder contents is optional; leave files but unused.
        defaults.set(CourseLibrary.currentSchemaVersion, forKey: CourseLibrary.schemaMigrationKey)
        try? modelContext.save()
    }

    func allCourses() -> [LocalCourse] {
        let descriptor = FetchDescriptor<LocalCourse>(
            sortBy: [SortDescriptor(\.title)]
        )
        return (try? modelContext.fetch(descriptor)) ?? []
    }

    func course(id: String) -> LocalCourse? {
        var descriptor = FetchDescriptor<LocalCourse>(predicate: #Predicate { $0.id == id })
        descriptor.fetchLimit = 1
        return try? modelContext.fetch(descriptor).first
    }

    func recentCourses(limit: Int = 50) -> [LocalCourse] {
        allCourses()
            .filter { $0.lastOpenedAt != nil }
            .sorted { ($0.lastOpenedAt ?? .distantPast) > ($1.lastOpenedAt ?? .distantPast) }
            .prefix(limit)
            .map { $0 }
    }

    @discardableResult
    func registerInstalled(manifest: CourseManifest) throws -> LocalCourse {
        let cover: String? = {
            let coverURL = try? CourseLibrary.fileURL(courseId: manifest.id, resourcePath: "cover.jpg")
            if let coverURL, FileManager.default.fileExists(atPath: coverURL.path) {
                return "cover.jpg"
            }
            return nil
        }()

        if let existing = course(id: manifest.id) {
            existing.title = manifest.title
            existing.mode = manifest.albumMode
            existing.updatedAtRemote = manifest.updatedAt
            existing.itemCount = manifest.items.count
            existing.coverRelativePath = cover
            existing.installedAt = Date()
            try modelContext.save()
            return existing
        }

        let local = LocalCourse(
            id: manifest.id,
            title: manifest.title,
            mode: manifest.albumMode,
            updatedAtRemote: manifest.updatedAt,
            itemCount: manifest.items.count,
            coverRelativePath: cover
        )
        modelContext.insert(local)
        try modelContext.save()
        return local
    }

    func shouldOverwrite(existing: LocalCourse, remoteUpdatedAt: String) -> Bool {
        remoteUpdatedAt > existing.updatedAtRemote
    }

    func bumpRecent(_ course: LocalCourse) {
        course.lastOpenedAt = Date()
        try? modelContext.save()
    }

    func deactivateAllSessions() {
        let sessions = (try? modelContext.fetch(FetchDescriptor<StudySession>())) ?? []
        for s in sessions where s.isActive { s.isActive = false }
        settings().activeSessionId = nil
    }

    @discardableResult
    func startOrResumeSession(course: LocalCourse, itemIndex: Int) throws -> StudySession {
        let manifest = try CourseLibrary.loadManifest(courseId: course.id)
        guard itemIndex >= 0, itemIndex < manifest.items.count else {
            throw CoursePackError.invalidManifest("item index out of range")
        }

        // Resume matching active/recent session for same course+item
        let sessions = (try? modelContext.fetch(FetchDescriptor<StudySession>())) ?? []
        if let existing = sessions.first(where: { $0.courseId == course.id && $0.itemIndex == itemIndex }) {
            deactivateAllSessions()
            existing.isActive = true
            existing.updatedAt = Date()
            settings().activeSessionId = existing.id
            bumpRecent(course)
            try modelContext.save()
            return existing
        }

        let item = manifest.items[itemIndex]
        let (mode, resources, title): (StudyMode, SessionResources, String) = {
            switch item {
            case .single(let resourceId):
                let res = manifest.resource(id: resourceId)
                let kind = res?.kind ?? "pdf"
                let mode: StudyMode
                switch kind {
                case "video": mode = .singleVideo
                case "audio": mode = .singleAudio
                default: mode = .singlePdf
                }
                var ids = SessionResources()
                switch mode {
                case .singleVideo: ids.video = resourceId
                case .singleAudio: ids.audio = resourceId
                default: ids.pdf = resourceId
                }
                return (mode, ids, res?.name ?? course.title)
            case .combo(let studyMode, let resourceIds):
                return (studyMode, resourceIds, "\(course.title) · \(studyMode.label)")
            }
        }()

        deactivateAllSessions()
        let session = StudySession(
            title: title,
            mode: mode,
            resourceIds: resources,
            courseId: course.id,
            itemIndex: itemIndex,
            isActive: true
        )
        modelContext.insert(session)
        settings().activeSessionId = session.id
        bumpRecent(course)
        try modelContext.save()
        return session
    }

    func itemCount(courseId: String) throws -> Int {
        try CourseLibrary.loadManifest(courseId: courseId).items.count
    }

    func hasNextItem(session: StudySession) -> Bool {
        guard let courseId = session.courseId, let index = session.itemIndex else { return false }
        guard let count = try? itemCount(courseId: courseId) else { return false }
        return index < count - 1
    }

    /// Advance album/course to the next manifest item (F067–F071).
    @discardableResult
    func advanceToNextItem(session: StudySession) throws -> StudySession? {
        guard let courseId = session.courseId,
              let currentIndex = session.itemIndex,
              let course = course(id: courseId) else { return nil }
        let manifest = try CourseLibrary.loadManifest(courseId: courseId)
        let nextIndex = currentIndex + 1
        guard nextIndex < manifest.items.count else { return nil }

        session.mediaTime = 0
        session.pdfPage = 1
        session.splitRatio = 0.55
        session.playerExpanded = false
        session.updatedAt = Date()

        let item = manifest.items[nextIndex]
        let (mode, resources, title): (StudyMode, SessionResources, String) = {
            switch item {
            case .single(let resourceId):
                let res = manifest.resource(id: resourceId)
                let kind = res?.kind ?? "pdf"
                let mode: StudyMode
                switch kind {
                case "video": mode = .singleVideo
                case "audio": mode = .singleAudio
                default: mode = .singlePdf
                }
                var ids = SessionResources()
                switch mode {
                case .singleVideo: ids.video = resourceId
                case .singleAudio: ids.audio = resourceId
                default: ids.pdf = resourceId
                }
                return (mode, ids, res?.name ?? course.title)
            case .combo(let studyMode, let resourceIds):
                return (studyMode, resourceIds, "\(course.title) · \(studyMode.label)")
            }
        }()

        session.itemIndex = nextIndex
        session.mode = mode
        session.resourceIds = resources
        session.title = title
        session.isActive = true
        settings().activeSessionId = session.id
        bumpRecent(course)
        try modelContext.save()
        return session
    }
}
