//
// Copyright 2023, 2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//

import OSLog
import Sentry

/// A simple wrapper around OSSignposter for easy performance testing.
class Signposter {
    /// The underlying signposter.
    private let signposter = OSSignposter(subsystem: subsystem, category: category)
    /// A logger instance to capture any errors.
    private let logger = Logger(subsystem: subsystem, category: category)
    
    /// Signpost name constants.
    enum Name {
        static let login: StaticString = "Login"
        static let firstSync: StaticString = "FirstSync"
        static let firstRooms: StaticString = "FirstRooms"
        static let roomFlow: StaticString = "RoomFlow"
        
        static let appStartup = "AppStartup"
        static let appStarted = "AppStarted"
        
        static let homeserver = "homeserver"
    }
    
    static let subsystem = "ElementX"
    static let category = "PerformanceTests"
    
    // MARK: - App Startup
    
    private var appStartupTransaction: Span?
    
    // We have a manual start method because we need to configure the ServiceLocator *before* we configure
    // Sentry but this class is created in the AnalyticsService and so spans and transactions are dropped
    // until Sentry has been configured. Therefore doing this in the init doesn't work.
    func start() {
        appStartupTransaction = SentrySDK.startTransaction(name: Name.appStartup, operation: Name.appStarted)
    }
    
    // MARK: - Login
    
    private var loginState: OSSignpostIntervalState?
    private var loginTransaction: Span?
    private var loginSpan: Span?
    
    func beginLogin() {
        loginState = signposter.beginInterval(Name.login)
        loginTransaction = SentrySDK.startTransaction(name: "\(Name.login)", operation: "\(Name.login)")
        loginSpan = appStartupTransaction?.startChild(operation: "\(Name.login)", description: "\(Name.login)")
    }
    
    func endLogin() {
        guard let loginState else {
            logger.error("Missing login state.")
            return
        }
        
        signposter.endInterval(Name.login, loginState)
        loginTransaction?.finish()
        loginSpan?.finish()
        
        self.loginState = nil
        loginTransaction = nil
        loginSpan = nil
    }
    
    // MARK: - FirstSync
    
    private var firstSyncState: OSSignpostIntervalState?
    private var firstSyncTransaction: Span?
    private var firstSyncSpan: Span?
    
    func beginFirstSync(serverName: String) {
        appStartupTransaction?.setTag(value: serverName, key: Name.homeserver)
        
        firstSyncState = signposter.beginInterval(Name.firstSync)
        
        firstSyncTransaction = SentrySDK.startTransaction(name: "\(Name.firstSync)", operation: "\(Name.firstSync)")
        firstSyncTransaction?.setTag(value: serverName, key: Name.homeserver)
        
        firstSyncSpan = appStartupTransaction?.startChild(operation: "\(Name.firstSync)", description: "\(Name.firstSync)")
    }
    
    func endFirstSync() {
        guard let firstSyncState else { return }
        
        signposter.endInterval(Name.firstSync, firstSyncState)
        firstSyncTransaction?.finish()
        firstSyncSpan?.finish()
        
        self.firstSyncState = nil
        firstSyncTransaction = nil
        firstSyncSpan = nil
    }
    
    // MARK: - FirstRooms
    
    private var firstRoomsState: OSSignpostIntervalState?
    private var firstRoomsTransaction: Span?
    private var firstRoomsSpan: Span?
    
    func beginFirstRooms() {
        firstRoomsState = signposter.beginInterval(Name.firstRooms)
        firstRoomsTransaction = SentrySDK.startTransaction(name: "\(Name.firstRooms)", operation: "\(Name.firstRooms)")
        firstRoomsSpan = appStartupTransaction?.startChild(operation: "\(Name.firstRooms)", description: "\(Name.firstRooms)")
    }
    
    func endFirstRooms() {
        defer {
            appStartupTransaction?.finish()
        }
        
        guard let firstRoomsState else { return }
        
        signposter.endInterval(Name.firstRooms, firstRoomsState)
        firstRoomsTransaction?.finish()
        firstRoomsSpan?.finish()
        
        self.firstRoomsState = nil
        firstRoomsTransaction = nil
        firstRoomsSpan = nil
    }
    
    // MARK: - Room Flow
    
    private var roomFlowState: OSSignpostIntervalState?
    
    func beginRoomFlow(_ name: String) {
        roomFlowState = signposter.beginInterval(Name.roomFlow)
        signposter.emitEvent("RoomName", "\(name, privacy: .auto(mask: .hash))")
    }
    
    func endRoomFlow() {
        guard let roomFlowState else { return }
        
        signposter.endInterval(Name.roomFlow, roomFlowState)
        self.roomFlowState = nil
    }
}
