//
//  PasswordTest.swift
//  passKitTests
//
//  Created by Danny Moesch on 02.05.18.
//  Copyright © 2018 Bob Sun. All rights reserved.
//

import XCTest

@testable import passKit

class PasswordTest: XCTestCase {

    override static func setUp() {
        super.setUp()
        SharedDefaults[.isHideUnknownOn] = false
        SharedDefaults[.isHideOTPOn] = false
    }

    func testUrl() {
        let password = getPasswordObjectWith(content: "")

        XCTAssertEqual(password.url, PASSWORD_URL)
        XCTAssertEqual(password.namePath, PASSWORD_PATH)
    }

    func testEmptyFile() {
        [
            "",
            "\n",
        ].forEach { fileContent in
            let password = getPasswordObjectWith(content: fileContent)

            XCTAssertEqual(password.password, "")
            XCTAssertEqual(password.plainData, fileContent.data(using: .utf8))
            XCTAssertEqual(password.additionsPlainText, "")
            XCTAssert(password.getFilteredAdditions().isEmpty)
            XCTAssertEqual(password.numberOfUnknowns, 0)

            XCTAssertNil(password.username)
            XCTAssertNil(password.urlString)
            XCTAssertNil(password.login)

            XCTAssertEqual(password.numberOfOtpRelated, 0)
        }
    }

    func testEmptyPassword() {
        let fileContent = "\n\(LOGIN_FIELD.asString)"
        let password = getPasswordObjectWith(content: fileContent)

        assertDefaults(in: password, with: "", and: LOGIN_FIELD.asString)

        XCTAssert(LOGIN_FIELD ∉ password)

        XCTAssertNil(password.username)
        XCTAssertNil(password.urlString)
        XCTAssertEqual(password.login, LOGIN_FIELD.content)
    }

    func testSimplePasswordFile() {
        let additions = SECURE_URL_FIELD | LOGIN_FIELD | USERNAME_FIELD | NOTE_FIELD
        let fileContent = PASSWORD_STRING | additions
        let password = getPasswordObjectWith(content: fileContent)

        assertDefaults(in: password, with: PASSWORD_STRING, and: additions)

        XCTAssert(SECURE_URL_FIELD ∈ password)
        XCTAssert(LOGIN_FIELD ∉ password)
        XCTAssert(USERNAME_FIELD ∉ password)
        XCTAssert(NOTE_FIELD ∈ password)

        XCTAssertEqual(password.urlString, SECURE_URL_FIELD.content)
        XCTAssertEqual(password.login, LOGIN_FIELD.content)
        XCTAssertEqual(password.username, USERNAME_FIELD.content)
    }

    func testTwoPasswords() {
        let additions = "efgh5678" | INSECURE_URL_FIELD
        let fileContent = PASSWORD_STRING | additions
        let password = getPasswordObjectWith(content: fileContent)

        XCTAssertEqual(password.password, PASSWORD_STRING)
        XCTAssertEqual(password.plainData, fileContent.data(using: .utf8))
        XCTAssertEqual(password.additionsPlainText, additions)
        XCTAssertEqual(password.numberOfUnknowns, 1)

        XCTAssert(INSECURE_URL_FIELD ∈ password)
        XCTAssert(Constants.unknown(1) => "efgh5678" ∈ password)

        XCTAssertNil(password.username)
        XCTAssertEqual(password.urlString, INSECURE_URL_FIELD.content)
        XCTAssertNil(password.login)

        XCTAssertEqual(password.numberOfOtpRelated, 0)
    }

    func testNoPassword() {
        let fileContent = SECURE_URL_FIELD | NOTE_FIELD
        let password = getPasswordObjectWith(content: fileContent)

        assertDefaults(in: password, with: SECURE_URL_FIELD.asString, and: NOTE_FIELD.asString)

        XCTAssert(NOTE_FIELD ∈ password)

        XCTAssertNil(password.username)
        XCTAssertNil(password.urlString)
        XCTAssertNil(password.login)
    }

    func testDuplicateKeys() {
        let additions = SECURE_URL_FIELD | INSECURE_URL_FIELD
        let fileContent = PASSWORD_STRING | additions
        let password = getPasswordObjectWith(content: fileContent)

        assertDefaults(in: password, with: PASSWORD_STRING, and: additions)

        XCTAssert(SECURE_URL_FIELD ∈ password)
        XCTAssert(INSECURE_URL_FIELD ∈ password)

        XCTAssertNil(password.username)
        XCTAssertEqual(password.urlString, SECURE_URL_FIELD.content)
        XCTAssertNil(password.login)
    }

    func testUnknownKeys() {
        let value1 = "value 1"
        let value2 = "value 2"
        let value3 = "value 3"
        let value4 = "value 4"
        let additions = value1 | NOTE_FIELD | value2 | value3 | SECURE_URL_FIELD | value4
        let fileContent = PASSWORD_STRING | additions
        let password = getPasswordObjectWith(content: fileContent)

        XCTAssertEqual(password.password, PASSWORD_STRING)
        XCTAssertEqual(password.plainData, fileContent.data(using: .utf8))
        XCTAssertEqual(password.additionsPlainText, additions)
        XCTAssertEqual(password.numberOfUnknowns, 4)

        XCTAssert(Constants.unknown(1) => value1 ∈ password)
        XCTAssert(NOTE_FIELD ∈ password)
        XCTAssert(Constants.unknown(2) => value2 ∈ password)
        XCTAssert(Constants.unknown(3) => value3 ∈ password)
        XCTAssert(SECURE_URL_FIELD ∈ password)
        XCTAssert(Constants.unknown(4) => value4 ∈ password)

        XCTAssertNil(password.username)
        XCTAssertEqual(password.urlString, SECURE_URL_FIELD.content)
        XCTAssertNil(password.login)

        XCTAssertEqual(password.numberOfOtpRelated, 0)
    }

    func testPasswordFileWithOtpToken() {
        let additions = NOTE_FIELD | TOTP_URL
        let fileContent = PASSWORD_STRING | additions
        let password = getPasswordObjectWith(content: fileContent)

        XCTAssertEqual(password.password, PASSWORD_STRING)
        XCTAssertEqual(password.plainData, fileContent.data(using: .utf8))
        XCTAssertEqual(password.additionsPlainText, additions)
        XCTAssertEqual(password.numberOfUnknowns, 0)

        XCTAssertEqual(password.numberOfOtpRelated, 1)
        XCTAssertEqual(password.otpType, .totp)
        XCTAssertNotNil(password.currentOtp)
    }

    func testFirstLineIsOtpToken() {
        let password = getPasswordObjectWith(content: TOTP_URL)

        XCTAssertEqual(password.password, TOTP_URL)
        XCTAssertEqual(password.plainData, TOTP_URL.data(using: .utf8))
        XCTAssertEqual(password.additionsPlainText, "")
        XCTAssertEqual(password.numberOfUnknowns, 0)

        XCTAssertNil(password.username)
        XCTAssertNil(password.urlString)
        XCTAssertNil(password.login)

        XCTAssertEqual(password.numberOfOtpRelated, 0)
        XCTAssertEqual(password.otpType, .totp)
        XCTAssertNotNil(password.currentOtp)
    }

    func testOtpTokenAsField() {
        let additions = TOTP_URL_FIELD.asString
        let fileContent = PASSWORD_STRING | additions
        let password = getPasswordObjectWith(content: fileContent)

        XCTAssertEqual(password.password, PASSWORD_STRING)
        XCTAssertEqual(password.plainData, fileContent.data(using: .utf8))
        XCTAssertEqual(password.additionsPlainText, additions)
        XCTAssertEqual(password.numberOfUnknowns, 0)

        XCTAssertEqual(password.numberOfOtpRelated, 1)
        XCTAssertEqual(password.otpType, .totp)
        XCTAssertNotNil(password.currentOtp)
    }

    func testOtpTokenFromFields() {
        let additions =
            Constants.OTP_SECRET => "secret" |
            Constants.OTP_TYPE => "hotp" |
            Constants.OTP_COUNTER => "12" |
            Constants.OTP_DIGITS => "7"
        let fileContent = PASSWORD_STRING | additions
        let password = getPasswordObjectWith(content: fileContent)

        XCTAssertEqual(password.password, PASSWORD_STRING)
        XCTAssertEqual(password.plainData, fileContent.data(using: .utf8))
        XCTAssertEqual(password.additionsPlainText, additions)
        XCTAssertEqual(password.numberOfUnknowns, 0)

        XCTAssertEqual(password.numberOfOtpRelated, 4)
        XCTAssertEqual(password.otpType, .hotp)
        XCTAssertNotNil(password.currentOtp)
    }

    func testWrongOtpToken() {
        let fileContent = "otpauth://htop/blabla"
        let password = getPasswordObjectWith(content: fileContent)

        XCTAssertEqual(password.password, fileContent)
        XCTAssertEqual(password.plainData, fileContent.data(using: .utf8))
        XCTAssert(password.additionsPlainText.isEmpty)
        XCTAssertEqual(password.numberOfUnknowns, 0)

        XCTAssertEqual(password.numberOfOtpRelated, 0)
        XCTAssertEqual(password.otpType, OtpType.none)
        XCTAssertNil(password.currentOtp)
    }

    func testEmptyMultilineValues() {
        let additions = MULTILINE_BLOCK_START | "\n" | MULTILINE_BLOCK_START | " \n" | NOTE_FIELD | MULTILINE_LINE_START
        let fileContent = PASSWORD_STRING | additions
        let password = getPasswordObjectWith(content: fileContent)

        assertDefaults(in: password, with: PASSWORD_STRING, and: additions)

        XCTAssert(MULTILINE_BLOCK_START.title => "" ∈ password)
        XCTAssert(MULTILINE_BLOCK_START.title => "" ∈ password)
        XCTAssert(NOTE_FIELD ∈ password)
        XCTAssert(MULTILINE_LINE_START.title => "" ∈ password)
    }

    func testMultilineValues() {
        let lineBreakField = "with line breaks" => "|\n  This is \n   text spread over \n  multiple lines!  "
        let noLineBreakField = "without line breaks" => " > \n This is \n  text spread over\n   multiple lines!"
        let additions = lineBreakField | NOTE_FIELD | noLineBreakField
        let fileContent = PASSWORD_STRING | additions
        let password = getPasswordObjectWith(content: fileContent)

        assertDefaults(in: password, with: PASSWORD_STRING, and: additions)

        XCTAssert(lineBreakField.title => "This is \n text spread over \nmultiple lines!" ∈ password)
        XCTAssert(NOTE_FIELD ∈ password)
        XCTAssert(noLineBreakField.title => "This is   text spread over   multiple lines!" ∈ password)
    }

    func testMultilineValuesMixed() {
        let lineBreakField = "with line breaks" => "|\n  This is \n  \(HINT_FIELD.asString) spread over\n multiple lines!"
        let noLineBreakField = "without line breaks" => " > \n This is \n | \n text spread over\nmultiple lines!"
        let additions = lineBreakField | noLineBreakField | NOTE_FIELD
        let fileContent = PASSWORD_STRING | additions
        let password = getPasswordObjectWith(content: fileContent)

        XCTAssertEqual(password.password, PASSWORD_STRING)
        XCTAssertEqual(password.plainData, fileContent.data(using: .utf8))
        XCTAssertEqual(password.additionsPlainText, additions)

        XCTAssert(lineBreakField.title => "This is \n\(HINT_FIELD.asString) spread over" ∈ password)
        XCTAssert(Constants.unknown(1) => " multiple lines!" ∈ password)
        XCTAssert(noLineBreakField.title => "This is  |  text spread over" ∈ password)
        XCTAssert(Constants.unknown(2) => "multiple lines!" ∈ password)
        XCTAssert(NOTE_FIELD ∈ password)
    }

    func testUpdatePassword() {
        let password = getPasswordObjectWith(content: "")
        XCTAssertEqual(password.changed, 0)

        password.updatePassword(name: "password", url: PASSWORD_URL, plainText: "")
        XCTAssertEqual(password.changed, 0)

        password.updatePassword(name: "", url: PASSWORD_URL, plainText: "a")
        XCTAssertEqual(password.changed, 2)

        password.updatePassword(name: "", url: URL(fileURLWithPath: "/some/path/"), plainText: "a")
        XCTAssertEqual(password.changed, 3)

        password.updatePassword(name: "", url: PASSWORD_URL, plainText: "")
        XCTAssertEqual(password.changed, 3)
    }

    func testOtpStringsNoOtpToken() {
        let password = getPasswordObjectWith(content: "")
        let otpStrings = password.getOtpStrings()

        XCTAssertNil(otpStrings)
    }

    func testOtpStringsTotpToken() {
        let password = getPasswordObjectWith(content: TOTP_URL)
        let otpStrings = password.getOtpStrings()
        let otpDescription = otpStrings!.description

        XCTAssertNotNil(otpStrings)
        XCTAssert(otpDescription.hasPrefix("TimeBased".localize() + " ("))
        XCTAssert(otpDescription.hasSuffix(")"))
    }

    func testOtpStringsHotpToken() {
        let password = getPasswordObjectWith(content: HOTP_URL)
        let otpStrings = password.getOtpStrings()

        XCTAssertNotNil(otpStrings)
        XCTAssertEqual(otpStrings!.description, "HmacBased".localize())
    }
}
