import Vapor
import Fluent
import Clairvoyant
import ClairvoyantBinaryCodable
import ClairvoyantVapor

var server: SQLiteDatabase!

private var provider: VaporMetricProvider! = nil

// configures your application
public func configure(_ app: Application) async throws {
    let storageFolder = URL(fileURLWithPath: app.directory.resourcesDirectory)

    let logFolder = storageFolder.appendingPathComponent("logs")
    let monitor = MetricObserver(
        logFileFolder: logFolder,
        logMetricId: "schafkopf.log")
    MetricObserver.standard = monitor

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

    let configPath = URL(fileURLWithPath: app.directory.resourcesDirectory)
        .appendingPathComponent("config.json")
    let configuration: Configuration

    do {
        configuration = try Configuration(loadFromUrl: configPath)
    } catch {
        _ = try? await status.update(.initializationFailure)
        monitor.log("Failed to read configuration: \(error)")
        // Note: If configuration can't be loaded, then the server will run on the wrong port
        // and access to metrics is impossible, since no tokens are loaded
        return
    }

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

    // Set target environment
    app.environment = .production

    if !configuration.production {
        app.logger.logLevel = .info
        log("[DEVELOPMENT] Using in-memory database")
        app.databases.use(.sqlite(.memory), as: .sqlite)
    } else {
        app.logger.logLevel = .notice
        let dbFile = storageFolder.appendingPathComponent("db.sqlite").path
        log("[PRODUCTION] Using database at \(dbFile)")
        app.databases.use(.sqlite(.file(dbFile)), as: .sqlite)
    }
    app.migrations.add(UserTableMigration())
    app.migrations.add(PasswordResetMigration())

    do {
        try await app.autoMigrate()
    } catch {
        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)
    
    // Gracefully shut down by closing potentially open socket
    DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + .seconds(5)) {
        _ = app.server.onShutdown.always { _ in
            server.disconnectAllSockets()
        }
    }

    // register routes
    routes(app)

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

    _ = try? await status.update(.nominal)
}

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