//
//  MailServerConfigurationTest.swift
//  enzevalos_iphoneTests
//
//  Created by Oliver Wiese on 22.03.19.
//  Copyright © 2019 fu-berlin. All rights reserved.
//

import XCTest

@testable import enzevalos_iphone

class MailServerConfigurationTest: XCTestCase, MailSessionListener{
    
    let datahandler = DataHandler.handler
    let mailHandler = AppDelegate.getAppDelegate().mailHandler
    let pgp = SwiftPGP()
    
    let msg1 = "* OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE AUTH=PLAIN AUTH=LOGIN] Dovecot ready.\r\n"
    let msg2 = "1 LOGIN \"Adasd@XXXXX.YYY\" adafa\r\n"
    let msg3 = "1 NO [AUTHENTICATIONFAILED] Authentication failed"
    let msg4 = "* OK [CAPABILITY IMAP4 IMAP4REV1 STARTTLS CHILDREN I18NLEVEL=1 IDLE LIST-EXTENDED LITERAL+ MULTIAPPEND NAMESPACE QUOTA SORT THREAD=REFERENCES UIDPLUS UNSELECT LOGINDISABLED] perdition ready on XXXXX 0019343e4\r\n"
    let msg5 = "1 STARTTLS\r\n"
    let msg6 = "1 OK Begin TLS negotiation now\r\n"
    let msg7 = "* OK XXX ready for requests from 12.34.567.89 e30mc94846126lfn\r\n"
    let msg8 = "1 CAPABILITY\r\n"
    let msg9 = "* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA ID XLIST CHILDREN X-GM-EXT-1 XYZZY SASL-IR AUTH=XOAUTH2 AUTH=PLAIN AUTH=PLAIN-CLIENTTOKEN AUTH=OAUTHBEARER AUTH=XOAUTH\r\n1 OK Thats all she wrote! e30mc94846126lfn\r\n"
    let msg10 = "2 LOGIN \"sadasfai@asfaf.com\" asdadf3\r\n"
    let msg11 = "2 NO [AUTHENTICATIONFAILED] Invalid credentials (Failure)\r\n"
    
    let noJson = ["2", "5"]
    
    var result: Bool?
    var expect: XCTestExpectation?
    
    var userAdr: String?
    
    override func setUp() {
        super.setUp()
        datahandler.reset()
        pgp.resetKeychains()
        result = nil
        expect = nil
    }

    override func tearDown() {
        super.tearDown()
        datahandler.reset()
        pgp.resetKeychains()
        result = nil
        expect = nil
    }

    func testAuthParsing() {
        checkAuthParsing(msg: msg1, res: [MCOAuthType.saslPlain, MCOAuthType.saslLogin])
        checkAuthParsing(msg: msg2, res: [])
        checkAuthParsing(msg: msg3, res: [])
        checkAuthParsing(msg: msg4, res: [])
        checkAuthParsing(msg: msg5, res: [])
        checkAuthParsing(msg: msg6, res: [])
        checkAuthParsing(msg: msg7, res: [])
        checkAuthParsing(msg: msg8, res: [])
        checkAuthParsing(msg: msg9, res: [MCOAuthType.xoAuth2, MCOAuthType.saslPlain])
        checkAuthParsing(msg: msg10, res: [])
        checkAuthParsing(msg: msg11, res: [])
    }
    
    func checkAuthParsing(msg: String, res: [MCOAuthType]) {
        let auths = MCOAuthType.parseAuthType(msg: msg)
        var found = false
        for a in auths {
            for r in res {
                if r.rawValue == a.rawValue {
                    found = true
                    break;
                }
            }
            XCTAssertTrue(found, "Too many authtypes! \(a) should be not in \(msg)")
            found = false
        }
        for a in res {
            for r in auths {
                if r.rawValue == a.rawValue {
                    found = true
                    break;
                }
            }
            XCTAssertTrue(found, "Too few authtypes! \(a) is not in \(msg)")
            found = false
        }
    }
    
    func testHappyPath() {
        let accounts = MailServerConfigurationTest.loadAccounts()
        guard accounts.count != 0 else {
            XCTFail("No account for testing!")
            return
        }
        for (key, account) in accounts {
            testJsonFile(correctPW: true, account: account, imap: true, shouldFail: noJson.contains(key))
            testJsonFile(correctPW: true, account: account, imap: false, shouldFail: noJson.contains(key))
        }
    }
    
    func testJsonFileButWrongPW() {
        let accounts = MailServerConfigurationTest.loadAccounts()
        guard accounts.count > 0 else {
            XCTFail("No account for testing!")
            return
        }
        for (key, account) in accounts {
            testJsonFile(correctPW: false, account: account, imap: true, shouldFail: noJson.contains(key))
            testJsonFile(correctPW: false, account: account, imap: false, shouldFail: noJson.contains(key))
        }
        
    }
    
    func testJsonFile(correctPW: Bool, account: (name: String, pw: String), imap: Bool, shouldFail: Bool = false){
        setUp()
        var pw = "password"
        
        if correctPW {
            pw = account.pw
        }
        var session = MailSession(configSession: .SMTP, mailAddress: account.name, password: pw, username: nil)
        if imap {
            session = MailSession(configSession: .IMAP, mailAddress: account.name, password: pw, username: nil)
        }
        session.addListener(listener: self)
        XCTAssertEqual(account.name, session.username)
        XCTAssertEqual(account.name, session.mailAddr)
        XCTAssertEqual(pw, session.password)
        if shouldFail {
            XCTAssertFalse(session.loadFromProviderJson())
            tearDown()
            return
        }
        expect = expectation(description: "Login result")
        if  !session.loadFromProviderJson() {
            XCTFail("No provider json file!")
        }
        if !session.startTestingServerConfig() {
            XCTFail("No testing of config...")
        }
        if let expect = expect {
            var time = 40
            if !correctPW || shouldFail {
                time = 60
            }
            wait(for: [expect], timeout: TimeInterval(time))
        }
        if correctPW {
            XCTAssert(result ?? false, "\(account.name) failed to login")
        }
        else {
            if let res = result {
                XCTAssert(!res, "Wrong result for \(session.server.discription) and user: \(account.name)")
            } else {
                XCTFail("No results! for \(session.server.discription) and user: \(account.name)")
            }
        }
        tearDown()
    }
    
    func testNoJson() {
        let accounts = MailServerConfigurationTest.loadAccounts()
        guard let account = accounts["2"] else {
            XCTFail("No account for testing!")
            return
        }
        testFindServerConfig(correctPW: true, account: account, imap: true)
        testFindServerConfig(correctPW: true, account: account, imap: false)
        testFindServerConfig(correctPW: false, account: account, imap: true, shouldFail: true)
        testFindServerConfig(correctPW: false, account: account, imap: false, shouldFail: true)
    }
    
    func testFindServerConfig(correctPW: Bool, account: (name: String, pw: String), imap: Bool, shouldFail: Bool = false) {
        setUp()
        var pw = "password"
        
        if correctPW {
            pw = account.pw
        }
        var session = MailSession(configSession: .SMTP, mailAddress: account.name, password: pw, username: nil)
        if imap {
            session = MailSession(configSession: .IMAP, mailAddress: account.name, password: pw, username: nil)
        }
        session.addListener(listener: self)
        if session.loadFromProviderJson() {
            XCTFail("Wrong test case! Found in json file!")
        }
        if session.startTestingServerConfigFromList() {
            XCTFail("Wrong test case! Json file exists.")
        }
        if !session.startLongSearchOfServerConfig(hostFromAdr: false) {
            XCTFail("Could not start.")
        }
        expect = expectation(description: "Login result: \n \(session.server.discription) \n IMAP?: \(imap)")
        if let expect = expect {
            var waitingTime = 30
            if !correctPW {
                waitingTime = 250
            }
            wait(for: [expect], timeout: TimeInterval(waitingTime))
        }
        if let res = result {
            XCTAssertEqual(res, !shouldFail)
        }
        else {
            XCTFail("No result! We should be faster. \n \(session.server.discription) \n IMAP?: \(imap) Should fail? \(shouldFail)")
        }
        tearDown()
    }
    
    func testDetailSetup(account: (adr: String, pw: String, hostIMAP: String, portIMAP: String, conTypeIMAP: String, hostSMTP: String, portSMTP: String, conTypeSMTP: String), correctPW: Bool, imap: Bool,  shouldFail: Bool = false) -> Bool {
        var pw = "pw"
        if correctPW {
            pw = account.pw
        }
        setUp()
        var session = MailSession(configSession: .SMTP, mailAddress: account.adr, password: pw, username: account.adr)
        if imap {
            session = MailSession(configSession: .IMAP, mailAddress: account.adr, password: pw, username: account.adr)
            if let port = UInt32(account.portIMAP) {
                var connType = MCOConnectionType.TLS.rawValue
                if account.conTypeIMAP == "StartTLS" {
                    connType = MCOConnectionType.startTLS.rawValue
                }
                session.setServer(hostname: account.hostIMAP, port: port , connType: connType, authType: nil)
            }
            else {
                XCTFail("Could not parse account data")
            }
        } else {
            if let port = UInt32(account.portSMTP) {
                var connType = MCOConnectionType.TLS.rawValue
                if account.conTypeSMTP == "StartTLS" {
                    connType = MCOConnectionType.startTLS.rawValue
                }
                session.setServer(hostname: account.hostSMTP, port: port, connType: connType, authType: nil)
            }
            else {
                XCTFail("Could not parse account data")
            }
        }
        session.addListener(listener: self)
        if !session.startTestingServerConfig() {
            XCTFail("Could not test config.")
        }
        expect = expectation(description: "Login result")
        if let expect = expect {
            var time = 20
            if !correctPW {
                time = 50
            }
            if shouldFail {
                time = 60
            }
            wait(for: [expect], timeout: TimeInterval(time))
        }
        var response = false
        if let res = result {
            XCTAssertEqual(res, !shouldFail && correctPW)
            response = res
        } else {
            XCTFail("No results for \(account.adr) and host: \(session.server.discription)")
        }
        tearDown()
        return response
    }
    
    
    func testDetail(){
        let accounts = MailServerConfigurationTest.loadDetailAccounts()
        guard accounts.count > 0 else {
            XCTFail("No account for testing!")
            return
        }
        for (_, account) in accounts {
            var res = testDetailSetup(account: account, correctPW: true, imap: true, shouldFail: false)
            XCTAssertTrue(res, "Failed for \(account.adr)")
            res = testDetailSetup(account: account, correctPW: true, imap: false, shouldFail: false)
            XCTAssertTrue(res, "Failed for \(account.adr)")
            res = testDetailSetup(account: account, correctPW: false, imap: true, shouldFail: true)
            XCTAssertFalse(res, "Failed for \(account.adr)")
            res = testDetailSetup(account: account, correctPW: false, imap: false, shouldFail: true)
            XCTAssertFalse(res, "Failed for \(account.adr)")
        }
    }
    
    func testDetailSMTP() {
        let accounts = MailServerConfigurationTest.loadAccounts()
        guard let account = accounts["1"] else {
            XCTFail("No account for testing!")
            return
        }
        let session = MailSession(configSession: .SMTP, mailAddress: "text@example.com", password: "password", username: "user")
        session.addListener(listener: self)
        session.username = account.name
        session.mailAddr = account.name
        session.password = account.pw
        session.setServer(hostname: "smtp.web.de", port: 587, connType: MCOConnectionType.startTLS.rawValue, authType: nil)
        if !session.startTestingServerConfig() {
            XCTFail("Could not test config.")
        }
        expect = expectation(description: "Login result")
        if let expect = expect {
            wait(for: [expect], timeout: 60)
        }
        XCTAssert(result ?? false, "Faild result for: \(account.name)")
    }
    
    
    func testMailFetch() {
        let accounts = MailServerConfigurationTest.loadDetailAccounts()
        guard accounts.count > 0 else {
            XCTFail("No account for testing!")
            return
        }
        if let account = accounts["1"]  {
            setAccount(adr: account.adr, pw: account.pw, hostIMAP: account.hostIMAP, portIMAP: account.portIMAP, conTypeIMAP: account.conTypeIMAP)
        }
        else {
            XCTFail("No account for testing!")
        }
        
        
    }
    
    private func setAccount(adr: String, pw: String, hostIMAP: String, portIMAP: String, conTypeIMAP: String) {
        userAdr = adr
        // IMAP
        guard let imapPort = UInt32(portIMAP) else {
            XCTFail("Could not set up account. Wrong imap port: \(portIMAP)")
            return
        }
        var connType = MCOConnectionType.TLS
        if conTypeIMAP == "StartTLS" {
            connType = MCOConnectionType.startTLS
        }
        let promise = expectation(description: "Set up account: \(hostIMAP)")

        let server = MailServer(sessionType: .IMAP, username: adr, password: pw, hostname: hostIMAP, port: imapPort, connectionType: connType , authType: nil, callback: {(error: MailServerConnectionError?, server: MailServer) -> () in
            promise.fulfill()
            self.callbackSetup(error: error, server: server)
        } )
        XCTAssertTrue(server.findHost())
        wait(for: [promise], timeout: 1)
    }
    
    func testFinish(result: Bool) {
        guard let expect = expect else {
            XCTFail("No expectation!")
            return
        }
        self.result = result
        expect.fulfill()
    }
    
    private func callbackSetup(error: MailServerConnectionError?, server: MailServer) -> () {
        guard error == nil else {
            XCTFail("No connection to server! \(String(describing: error)) \(server.hostname) for \(server.discription)")
            return
        }
        let store = server.storeToUserDefaults(mailAddr: userAdr!)
        XCTAssertTrue(store)
        // Test fetching mails!
        let mailHandler = AppDelegate.getAppDelegate().mailHandler
        let promise = expectation(description: "Call for mails!")

        mailHandler.loadMailsForInbox(completionCallback: {(error: MailServerConnectionError?) -> () in
            promise.fulfill()
            print("Works!")
            self.loadMailsCallback(error: error)
        })
        wait(for: [promise], timeout: 90)
    }
    
    private func loadMailsCallback(error: MailServerConnectionError?) {
        guard error == nil else {
            XCTFail("Error! \(String(describing: error)) for \(String(describing: userAdr))")
            return
        }
        var mails = 0
        DataHandler.handler.allFolders.forEach{mails = mails + $0.counterMails}
        result = true
    }
    
    private static func loadAccounts() -> [String:(name: String, pw: String)] {
        let bundle = Bundle(for: self)
        var newAccounts = [String:(name: String, pw: String)]()
        guard let url = bundle.url(forResource: "accounts", withExtension: "json"), let data = try? Data(contentsOf: url),  let jsonDic = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves)  else {
            XCTFail()
            return [:]
        }
        if let dic = jsonDic as? Dictionary<String, Any>{
            if let accounts = dic["accounts"], let array = accounts as? Array<Any> {
                for elem in array {
                    if let account = elem as? Dictionary<String, String> {
                        if let id = account["id"], let username = account["username"] , let pw = account["password"] {
                            newAccounts[id] = (username, pw)
                        }
                    }
                }
            }
           
        }
        return newAccounts
    }
    
    private static func loadDetailAccounts() -> [String:(adr: String, pw: String, hostIMAP: String, portIMAP: String, conTypeIMAP: String, hostSMTP: String, portSMTP: String, conTypeSMTP: String)] {
        let bundle = Bundle(for: self)
        var newAccounts = [String:(adr: String, pw: String, hostIMAP: String, portIMAP: String, conTypeIMAP: String, hostSMTP: String, portSMTP: String, conTypeSMTP: String)]()
        guard let url = bundle.url(forResource: "accounts", withExtension: "json"), let data = try? Data(contentsOf: url),  let jsonDic = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves)  else {
            XCTFail("No file!")
            return [:]
        }
        if let dic = jsonDic as? Dictionary<String, Any>{
            if let accounts = dic["accounts"], let array = accounts as? Array<Any> {
                for elem in array {
                    if let account = elem as? Dictionary<String, String> {
                        if let id = account["id"], let username = account["username"] , let pw = account["password"], let hostIMAP = account["hostIMAP"], let portIMAP = account["portIMAP"], let conTypeIMAP = account["conTypeIMAP"], let hostSMTP = account["hostSMTP"], let portSMTP = account["portSMTP"], let conTypeSMTP = account["conTypeSMTP"]{
                            newAccounts[id] = (username, pw, hostIMAP, portIMAP, conTypeIMAP, hostSMTP, portSMTP, conTypeSMTP)
                        }
                    }
                }
            }
            
        }
        XCTAssertGreaterThan(newAccounts.count, 0)
        return newAccounts
    }
    
    private static func extractProvider(adr: String) -> String? {
        return adr.components(separatedBy: "@").last
    }
    
    
}