import Foundation
import SwiftUI

final class DatabaseList: ObservableObject {

    @AppStorage("databaseList")
    var databaseListData: Data?

    @Published
    var databases: [DatabaseFile] = []

    private var isLoaded = false

    init() { }

    func load() {
        guard !isLoaded else {
            return
        }
        isLoaded = true
        guard let databaseListData else {
            print("No database list")
            return
        }
        let loaded: [DatabaseFile]
        do {
            loaded = try JSONDecoder()
                .decode([DatabaseFile].self, from: databaseListData)
            print("Found \(loaded.count) databases")
        } catch {
            print("Failed to load databases: \(error)")
            loaded = []
        }
        let missing = loadDatabases(missingFrom: loaded)
        DispatchQueue.main.async {
            self.databases = loaded + missing
        }
        if !missing.isEmpty {
            saveDatabaseList()
        }
    }

    private func loadDatabases(missingFrom list: [DatabaseFile]) -> [DatabaseFile] {
        let files: [URL]
        do {
            files = try FileManager.default.contentsOfDirectory(at: FileManager.default.documentDirectory, includingPropertiesForKeys: nil)
        } catch {
            print("Failed to read document directory: \(error)")
            return []
        }
        let missingDatabases = files.filter { file in
            guard file.pathExtension == "sqlite" else {
                return false
            }
            let fileName = file.lastPathComponent
            return !list.contains { $0.file == fileName }
        }.map {
            DatabaseFile(file: $0.lastPathComponent, name: $0.lastPathComponent, isDefault: false)
        }
        guard !missingDatabases.isEmpty else {
            return []
        }
        print("Found \(missingDatabases.count) missing databases")
        return missingDatabases
    }

    @discardableResult
    private func saveDatabaseList() -> Bool {
        do {
            print("Saving \(databases.count) databases")
            databaseListData = try JSONEncoder().encode(databases)
            return true
        } catch {
            print("Failed to save databases: \(error)")
            return false
        }
    }

    func importDatabase(at url: URL) {
        print("Importing database at \(url.path)")
        let localUrl = findNextLocalUrl(for: url)
        do {
            print("Copying to \(localUrl.path)")
            try FileManager.default.copyItem(at: url, to: localUrl)
        } catch {
            print("Failed to copy imported file: \(error)")
            return
        }
        let database = DatabaseFile(
            file: localUrl.lastPathComponent,
            name: localUrl.lastPathComponent,
            isDefault: false)
        databases.append(database)
        print("Successfully imported database")
        saveDatabaseList()
    }

    func findNextLocalUrl(for url: URL) -> URL {
        let fileName = url.deletingPathExtension().lastPathComponent
        let fileExtension = url.pathExtension
        let documentDirectory = FileManager.default.documentDirectory
        let normalUrl = documentDirectory.appendingPathComponent("\(fileName).\(fileExtension)")
        guard FileManager.default.fileExists(atPath: normalUrl.path) else {
            return normalUrl
        }
        var index = 0
        var url: URL
        repeat {
            index += 1
            url = documentDirectory.appendingPathComponent("\(fileName)-\(index).\(fileExtension)")
        } while FileManager.default.fileExists(atPath: url.path)
        return url
    }

    func setAsDefault(database: DatabaseFile) {
        guard let newDefault = databases.firstIndex(of: database) else {
            return
        }
        if let oldDefault = databases.firstIndex(where: { $0.isDefault }) {
            databases[oldDefault].isDefault = false
        }
        databases[newDefault].isDefault = true
        saveDatabaseList()
    }

    func update(name: String, for database: DatabaseFile) -> DatabaseFile? {
        guard let index = databases.firstIndex(of: database) else {
            return nil
        }
        databases[index].name = name
        saveDatabaseList()
        return databases[index]
    }

    func add(database: DatabaseFile) {
        defer { saveDatabaseList() }
        guard let index = databases.firstIndex(of: database) else {
            databases.append(database)
            return
        }
        databases[index] = database
    }

    func deleteDatabases(at indexSet: IndexSet) {
        var deleted = IndexSet()
        for index in indexSet {
            if deleteDatabase(at: index) {
                deleted.insert(index)
            }
        }
        guard !deleted.isEmpty else { return }
        databases.remove(atOffsets: deleted)
        saveDatabaseList()
    }

    private func deleteDatabase(at index: Int) -> Bool {
        let database = databases[index]
        let url = database.url
        guard FileManager.default.fileExists(atPath: url.path) else {
            return true
        }
        do {
            try FileManager.default.removeItem(at: url)
            return true
        } catch {
            print("Failed to delete database \(database.name): \(error)")
            return false
        }
    }
}
