//
// Copyright 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 XCTest

@testable import ElementX
import MatrixRustSDK

class RestorationTokenTests: XCTestCase {
    func testDecodeFromTokenV1() throws {
        // Given an encoded restoration token in the original format that only contains a Session from the SDK.
        let originalToken = RestorationTokenV1(session: Session(accessToken: "1234",
                                                                refreshToken: nil,
                                                                userId: "@user:example.com",
                                                                deviceId: "D3V1C3",
                                                                homeserverUrl: "https://matrix.example.com",
                                                                oidcData: nil,
                                                                slidingSyncVersion: .proxy(url: "https://sync.example.com")))
        let data = try JSONEncoder().encode(originalToken)
        
        // When decoding the data to the current restoration token format.
        let decodedToken = try JSONDecoder().decode(RestorationToken.self, from: data)
        
        // Then the output should be a valid token with the expected store directories.
        XCTAssertEqual(decodedToken.session, originalToken.session, "The session should not be changed.")
        XCTAssertNil(decodedToken.passphrase, "There should not be a passphrase.")
        XCTAssertNil(decodedToken.pusherNotificationClientIdentifier, "There should not be a push notification client ID.")
        XCTAssertEqual(decodedToken.sessionDirectories.dataDirectory, .sessionsBaseDirectory.appending(component: "@user_example.com"),
                       "The session directory should match the original location set by the Rust SDK from our base directory.")
        XCTAssertEqual(decodedToken.sessionDirectories.cacheDirectory, .sessionCachesBaseDirectory.appending(component: "@user_example.com"),
                       "The cache directory should be derived from the session directory but in the caches directory.")
    }
    
    func testDecodeFromTokenV4() throws {
        // Given an encoded restoration token in the 4th format that contains a stored session directory.
        let sessionDirectoryName = UUID().uuidString
        let originalToken = RestorationTokenV4(session: Session(accessToken: "1234",
                                                                refreshToken: "5678",
                                                                userId: "@user:example.com",
                                                                deviceId: "D3V1C3",
                                                                homeserverUrl: "https://matrix.example.com",
                                                                oidcData: "data-from-mas",
                                                                slidingSyncVersion: .proxy(url: "https://sync.example.com")),
                                               sessionDirectory: .sessionsBaseDirectory.appending(component: sessionDirectoryName),
                                               passphrase: "passphrase",
                                               pusherNotificationClientIdentifier: "pusher-identifier")
        let data = try JSONEncoder().encode(originalToken)
        
        // When decoding the data to the current restoration token format.
        let decodedToken = try JSONDecoder().decode(RestorationToken.self, from: data)
        
        // Then the output should be a valid token with the expected store directories.
        XCTAssertEqual(decodedToken.session, originalToken.session, "The session should not be changed.")
        XCTAssertEqual(decodedToken.passphrase, originalToken.passphrase, "The passphrase should not be changed.")
        XCTAssertEqual(decodedToken.pusherNotificationClientIdentifier, originalToken.pusherNotificationClientIdentifier,
                       "The push notification client identifier should not be changed.")
        XCTAssertEqual(decodedToken.sessionDirectories.dataDirectory, originalToken.sessionDirectory,
                       "The session directory should not be changed.")
        XCTAssertEqual(decodedToken.sessionDirectories.cacheDirectory, .sessionCachesBaseDirectory.appending(component: sessionDirectoryName),
                       "The cache directory should be derived from the session directory but in the caches directory.")
    }
    
    func testDecodeFromTokenV5() throws {
        // Given an encoded restoration token in the 5th format that contains separate directories for session data and caches.
        let sessionDirectoryName = UUID().uuidString
        let originalToken = RestorationTokenV5(session: Session(accessToken: "1234",
                                                                refreshToken: "5678",
                                                                userId: "@user:example.com",
                                                                deviceId: "D3V1C3",
                                                                homeserverUrl: "https://matrix.example.com",
                                                                oidcData: "data-from-mas",
                                                                slidingSyncVersion: .native),
                                               sessionDirectory: .sessionsBaseDirectory.appending(component: sessionDirectoryName),
                                               cacheDirectory: .sessionCachesBaseDirectory.appending(component: sessionDirectoryName),
                                               passphrase: "passphrase",
                                               pusherNotificationClientIdentifier: "pusher-identifier")
        let data = try JSONEncoder().encode(originalToken)
        
        // When decoding the data.
        let decodedToken = try JSONDecoder().decode(RestorationToken.self, from: data)
        
        // Then the output should be a valid token.
        XCTAssertEqual(decodedToken.session, originalToken.session, "The session should not be changed.")
        XCTAssertEqual(decodedToken.passphrase, originalToken.passphrase, "The passphrase should not be changed.")
        XCTAssertEqual(decodedToken.pusherNotificationClientIdentifier, originalToken.pusherNotificationClientIdentifier,
                       "The push notification client identifier should not be changed.")
        XCTAssertEqual(decodedToken.sessionDirectories.dataDirectory, originalToken.sessionDirectory,
                       "The session directory should not be changed.")
        XCTAssertEqual(decodedToken.sessionDirectories.cacheDirectory, originalToken.cacheDirectory,
                       "The cache directory should not be changed.")
    }
    
    func testDecodeFromCurrentToken() throws {
        // Given an encoded restoration token in the current format.
        let originalToken = RestorationToken(session: Session(accessToken: "1234",
                                                              refreshToken: "5678",
                                                              userId: "@user:example.com",
                                                              deviceId: "D3V1C3",
                                                              homeserverUrl: "https://matrix.example.com",
                                                              oidcData: "data-from-mas",
                                                              slidingSyncVersion: .native),
                                             sessionDirectories: .init(),
                                             passphrase: "passphrase",
                                             pusherNotificationClientIdentifier: "pusher-identifier")
        let data = try JSONEncoder().encode(originalToken)
        
        // When decoding the data.
        let decodedToken = try JSONDecoder().decode(RestorationToken.self, from: data)
        
        // Then the output should be a valid token.
        XCTAssertEqual(decodedToken, originalToken, "The token should remain identical.")
    }
}

struct RestorationTokenV1: Equatable, Codable {
    let session: MatrixRustSDK.Session
}

struct RestorationTokenV4: Equatable, Codable {
    let session: MatrixRustSDK.Session
    let sessionDirectory: URL
    let passphrase: String?
    let pusherNotificationClientIdentifier: String?
}

struct RestorationTokenV5: Equatable, Codable {
    let session: MatrixRustSDK.Session
    let sessionDirectory: URL
    let cacheDirectory: URL
    let passphrase: String?
    let pusherNotificationClientIdentifier: String?
}
