import Vapor
import Fluent
import Clairvoyant
import ClairvoyantBinaryCodable
import ClairvoyantVapor

var server: SQLiteDatabase!

private var provider: VaporMetricProvider! = nil
private var status: Metric<ServerStatus>!
private let scheduler = MultiThreadedEventLoopGroup(numberOfThreads: 2)

func configure(_ app: Application) async throws {
    let resourceFolder = URL(fileURLWithPath: app.directory.resourcesDirectory)
    let publicDirectory = app.directory.publicDirectory
    
    let configPath = URL(fileURLWithPath: app.directory.resourcesDirectory)
        .appendingPathComponent("config.json")
    let configuration = try Configuration(loadFromUrl: configPath)

    let logFolder = configuration.logURL(possiblyRelativeTo: resourceFolder)
    let monitor = MetricObserver(
        logFileFolder: logFolder,
        logMetricId: "schafkopf.log")
    MetricObserver.standard = monitor

    status = Metric<ServerStatus>(
        "schafkopf.status",
        name: "Status",
        description: "The main status of the server")
    
    try await status.update(.initializing)

    app.http.server.configuration.port = configuration.serverPort

    switch app.environment {
    case .production:
        app.logger.logLevel = .notice
        let dataDirectory = configuration.customDataDirectory(or: publicDirectory)
        let dbFile = dataDirectory.appendingPathComponent("db.sqlite").path
        log("[PRODUCTION] Using database at \(dbFile)")
        app.databases.use(.sqlite(.file(dbFile)), as: .sqlite)
    default:
        log("[DEVELOPMENT] Using in-memory database")
        app.databases.use(.sqlite(.memory), as: .sqlite)
    }

    app.migrations.add(UserTableMigration())
    app.migrations.add(PasswordResetMigration())

    do {
        try await app.autoMigrate()
    } catch {
        await monitor.log("Failed to migrate database: \(error)")
        try await status.update(.initializationFailure)
        return
    }

    // serve files from /Public folder
    app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))

    let db = app.databases.database(.sqlite, logger: .init(label: "Init"), on: app.databases.eventLoopGroup.next())!
    server = try await SQLiteDatabase(database: db, mail: configuration.mail)
    
    // register routes
    routes(app)

    // Expose metrics
    provider = .init(observer: monitor, accessManager: configuration.monitoringTokens)
    provider.asyncScheduler = scheduler
    provider.registerRoutes(app)
    monitor.saveCurrentListOfMetricsToLogFolder()

    try await status.update(.nominal)
    await server.printServerStartMessage()
}

func shutdown() {
    Task {
        await server.shutdown()
        try await scheduler.shutdownGracefully()
    }
}

func log(_ message: String) {
    guard let observer = MetricObserver.standard else {
        print(message)
        return
    }
    scheduler.schedule {
        await observer.log(message)
    }
}
