import Foundation
import GRDB

/// AppDatabase lets the application access the database.
///
/// It applies the pratices recommended at
/// <https://github.com/groue/GRDB.swift/blob/master/Documentation/GoodPracticesForDesigningRecordTypes.md>
struct AppDatabase {
    /// Creates an `AppDatabase`, and make sure the database schema is ready.
    init(_ dbWriter: any DatabaseWriter) throws {
        self.dbWriter = dbWriter
        try migrator.migrate(dbWriter)
    }
    
    /// Provides access to the database.
    ///
    /// Application can use a `DatabasePool`, while SwiftUI previews and tests
    /// can use a fast in-memory `DatabaseQueue`.
    ///
    /// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseconnections>
    private let dbWriter: any DatabaseWriter
    
    /// The DatabaseMigrator that defines the database schema.
    ///
    /// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/migrations>
    private var migrator: DatabaseMigrator {
        var migrator = DatabaseMigrator()
        
        #if DEBUG
        // Speed up development by nuking the database when migrations change
        // See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/migrations>
        migrator.eraseDatabaseOnSchemaChange = true
        #endif
        
        migrator.registerMigration("createLine") { db in
            // Create a table
            // See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseschema>
            try db.create(table: "Line") { t in
                t.autoIncrementedPrimaryKey("id")
                t.column("body", .text).notNull()
                t.column("chap", .integer).notNull()
                t.column("book", .text).notNull()
                t.column("verse", .integer)
            }

            try db.create(table: "Seg") { t in
                t.autoIncrementedPrimaryKey("id")
                t.column("seg_id", .integer).notNull()
                t.column("line_id", .integer).notNull()
                t.column("book", .text).notNull()
            }

            try db.create(table: "Ribbon") { t in
                t.autoIncrementedPrimaryKey("id")
                t.column("book", .text).notNull()
                t.column("scrollOffset", .integer).notNull()
                t.column("scrollId", .text)
            }

            try db.create(table: "SelectedRibbon") { t in
                t.autoIncrementedPrimaryKey("id")
                t.column("ribbonId", .integer).notNull()
            }

            try db.create(table: "ScrollState") { t in
                t.autoIncrementedPrimaryKey("id")
                t.column("scrollId", .text).notNull()
                t.column("scrollOffset", .integer).notNull()
            }

            try db.create(table: "foo3") { t in
                t.autoIncrementedPrimaryKey("id")
                t.column("ribbonId", .integer).notNull()
            }
        }

        
        // Migrations for future application versions will be inserted here:
        // migrator.registerMigration(...) { db in
        //     ...
        // }
        
        return migrator
    }
}

// MARK: - Database Access: Writes


func load<T: Decodable>(_ filename: String) -> T {
    let data: Data

    guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
        else {
            fatalError("Couldn't find \(filename) in main bundle.")
    }

    do {
        data = try Data(contentsOf: file)
    } catch {
        fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
    }

    do {
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    } catch {
        fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
    }
}

extension AppDatabase {

    func saveRibbon(_ ribbon: inout Ribbon) async throws {
        // if ribbon.name.isEmpty {
        //     throw ValidationError.missingName
        // }
        ribbon = try await dbWriter.write { [ribbon] db in
            try ribbon.saved(db)
        }
    }


    func saveSelectedRibbon(_ selectedRibbon: inout SelectedRibbon) async throws {
        // if ribbon.name.isEmpty {
        //     throw ValidationError.missingName
        // }
        try await dbWriter.write { [selectedRibbon] db in
            try selectedRibbon.update(db)
        }
    }

    func saveScrollState(_ scrollState: inout ScrollState) async throws {
        // if ribbon.name.isEmpty {
        //     throw ValidationError.missingName
        // }
        try await dbWriter.write { [scrollState] db in
            try scrollState.update(db)
        }
    }



    /// Create random Lines if the database is empty.
    func createRandomLinesIfEmpty() throws {

        let imports : JsonImport = load("john_export.json")


        try dbWriter.write { db in

//             try db.execute(sql: "DELETE FROM Line")
//             try db.execute(sql: "DELETE FROM Seg")
//             try db.execute(sql: "DELETE FROM Ribbon")
//             try db.execute(sql: "DELETE FROM SelectedRibbon")

            if try Line.all().isEmpty(db) {
                for l in imports.lines {
                    print("here")
                    print(l.body)
                    _ = try l.inserted(db)
                }

                for l in imports.segs {
                    print("here SEGS")
                    print(l.seg_id)
                    _ = try l.inserted(db)
                }

                // print("cat")
                // try createRandomLines(db)

                _ = try Ribbon(id: 1, book: "bible.john", scrollId: "1", scrollOffset: 0).inserted(db)
                _ = try Ribbon(id: 2, book: "bible.john", scrollId: "1", scrollOffset: 300).inserted(db)
                _ = try SelectedRibbon(id: 1, ribbonId: 1).inserted(db)
            }
        }
    }

//     static let uiTestLines = [
//         Line(id: nil, name: "Arthur", score: 5),
//         Line(id: nil, name: "Barbara", score: 6),
//         Line(id: nil, name: "Craig", score: 8),
//         Line(id: nil, name: "David", score: 4),
//         Line(id: nil, name: "Elena", score: 1),
//         Line(id: nil, name: "Frederik", score: 2),
//         Line(id: nil, name: "Gilbert", score: 7),
//         Line(id: nil, name: "Henriette", score: 3)]

//         func createLinesForUITests() throws {
//             try dbWriter.write { db in
//                 try AppDatabase.uiTestLines.forEach { Line in
//                     _ = try Line.inserted(db) // insert but ignore inserted id
//                 }
//             }
//         }

}

// MARK: - Database Access: Reads

// This demo app does not provide any specific reading method, and insteadKK
// gives an unrestricted read-only access to the rest of the application.
// In your app, you are free to choose another path, and define focused
// reading methods.
extension AppDatabase {
    /// Provides a read-only access to the database
    var reader: DatabaseReader {
        dbWriter
    }
}
