//
//  AnalyticsGeneratorTests.swift
//  
//
//  Created by Thibaut Schmitt on 06/09/2022.
//

import Foundation
import XCTest
import ToolCore

@testable import ResgenSwift

final class AnalyticsGeneratorTests: XCTestCase {
    
    private func getAnalyticsDefinition(
        id: String,
        path: String = "",
        action: String = "",
        category: String = "",
        name: String,
        type: AnalyticsDefinition.TagType,
        tags: [String]
    ) -> AnalyticsDefinition {
        let definition = AnalyticsDefinition(id: id, name: name, type: type)
        definition.tags = tags
        definition.path = path
        definition.action = action
        definition.category = category
        return definition
    }
    
    func testGeneratedExtensionContentFirebase() {
        // Given
        let sectionOne = AnalyticsCategory(id: "section_one")
        sectionOne.definitions = [
            getAnalyticsDefinition(id: "s1_def_one", name: "s1 def one", type: .screen, tags: ["ios", "iosonly"]),
            getAnalyticsDefinition(id: "s1_def_two", name: "s1 def two", type: .event, tags: ["ios", "iosonly"]),
        ]
        
        let sectionTwo = AnalyticsCategory(id: "section_two")
        sectionTwo.definitions = [
            getAnalyticsDefinition(id: "s2_def_one", name: "s2 def one", type: .screen, tags: ["ios","iosonly"]),
            getAnalyticsDefinition(id: "s2_def_two", name: "s2 def two", type: .event, tags: ["droid","droidonly"]),
        ]
        
        let sectionThree = AnalyticsCategory(id: "section_three")
        sectionThree.definitions = [
            getAnalyticsDefinition(id: "s3_def_one", name: "s3 def one", type: .screen, tags: ["droid","droidonly"]),
            getAnalyticsDefinition(id: "s3_def_two", name: "s3 def two", type: .event, tags: ["droid","droidonly"]),
        ]
        
        // When
        let extensionContent = AnalyticsGenerator.getExtensionContent(
            targets: [TrackerType.firebase],
            sections: [sectionOne, sectionTwo, sectionThree],
            tags: ["ios", "iosonly"],
            staticVar: false,
            extensionName: "GenAnalytics"
        )

        // Expect Analytics
        let expect = """
        // Generated by ResgenSwift.Analytics \(ResgenSwiftVersion)

        import FirebaseAnalytics

        // MARK: - Protocol

        protocol AnalyticsManagerProtocol {

            func logScreen(name: String, path: String)
            func logEvent(
                name: String,
                action: String,
                category: String,
                params: [String: Any]?
            )
        }

        // MARK: - Firebase

        class FirebaseAnalyticsManager: AnalyticsManagerProtocol {

            func logScreen(name: String, path: String) {
                var parameters = [
                    AnalyticsParameterScreenName: name as NSObject
                ]

                Analytics.logEvent(
                    AnalyticsEventScreenView,
                    parameters: parameters
                )
            }

            func logEvent(
                name: String,
                action: String,
                category: String,
                params: [String: Any]?
            ) {
                var parameters: [String:NSObject] = [
                    "action": action as NSObject,
                    "category": category as NSObject,
                ]

                if let supplementaryParameters = params {
                    for (newKey, newValue) in supplementaryParameters {
                        if parameters.contains(where: { (key: String, value: NSObject) in
                            key == newKey
                        }) {
                            continue
                        }

                        parameters[newKey] = newValue as? NSObject
                    }
                }

                Analytics.logEvent(
                    name.replacingOccurrences(of: [" "], with: "_"),
                    parameters: parameters
                )
            }
        }

        // MARK: - Manager

        class AnalyticsManager {

            static var shared = AnalyticsManager()

            // MARK: - Properties

            var managers: [AnalyticsManagerProtocol] = []

            private var isEnabled: Bool = true

            // MARK: - Methods

            func setAnalyticsEnabled(_ enable: Bool) {
                isEnabled = enable
            }

            func configure() {
                managers.append(FirebaseAnalyticsManager())
            }

            private func logScreen(name: String, path: String) {
                guard isEnabled else { return }

                managers.forEach { manager in
                    manager.logScreen(name: name, path: path)
                }
            }

            private func logEvent(
                name: String,
                action: String,
                category: String,
                params: [String: Any]?
            ) {
                guard isEnabled else { return }

                managers.forEach { manager in
                    manager.logEvent(
                        name: name,
                        action: action,
                        category: category,
                        params: params
                    )
                }
            }

            // MARK: - section_one

            func logScreenS1DefOne() {
                logScreen(
                    name: "s1 def one",
                    path: ""
                )
            }

            func logEventS1DefTwo() {
                logEvent(
                    name: "s1 def two",
                    action: "",
                    category: "",
                    params: [:]
                )
            }

            // MARK: - section_two

            func logScreenS2DefOne() {
                logScreen(
                    name: "s2 def one",
                    path: ""
                )
            }
        }
        
        """
        
        if extensionContent != expect {
            print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
        }
        XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
    }
    
    func testGeneratedExtensionContentMatomo() {
        // Given
        let sectionOne = AnalyticsCategory(id: "section_one")
        sectionOne.definitions = [
            getAnalyticsDefinition(id: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: .screen, tags: ["ios", "iosonly"]),
            getAnalyticsDefinition(id: "s1_def_two", action: "test", category: "test", name: "s1 def two", type: .event, tags: ["ios", "iosonly"]),
        ]
        
        let sectionTwo = AnalyticsCategory(id: "section_two")
        sectionTwo.definitions = [
            getAnalyticsDefinition(id: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: .screen, tags: ["ios","iosonly"]),
            getAnalyticsDefinition(id: "s2_def_two", action: "test", category: "test", name: "s2 def two", type: .event, tags: ["droid","droidonly"]),
        ]
        
        let sectionThree = AnalyticsCategory(id: "section_three")
        sectionThree.definitions = [
            getAnalyticsDefinition(id: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: .screen, tags: ["droid","droidonly"]),
            getAnalyticsDefinition(id: "s3_def_two", action: "test", category: "test", name: "s3 def two", type: .event, tags: ["droid","droidonly"]),
        ]
        
        // When
        let extensionContent = AnalyticsGenerator.getExtensionContent(
            targets: [TrackerType.matomo],
            sections: [sectionOne, sectionTwo, sectionThree],
            tags: ["ios", "iosonly"],
            staticVar: false,
            extensionName: "GenAnalytics"
        )
        // Expect Analytics
        let expect = """
        // Generated by ResgenSwift.Analytics \(ResgenSwiftVersion)

        import MatomoTracker

        // MARK: - Protocol

        protocol AnalyticsManagerProtocol {

            func logScreen(name: String, path: String)
            func logEvent(
                name: String,
                action: String,
                category: String,
                params: [String: Any]?
            )
        }

        // MARK: - Matomo

        class MatomoAnalyticsManager: AnalyticsManagerProtocol {

            // MARK: - Properties

            private var tracker: MatomoTracker

            // MARK: - Init

            init(siteId: String, url: String) {
                debugPrint("[Matomo service] Server URL: \\(url)")
                debugPrint("[Matomo service] Site ID: \\(siteId)")
                tracker = MatomoTracker(
                    siteId: siteId,
                    baseURL: URL(string: url)!
                )

                #if DEBUG
                    tracker.dispatchInterval = 5
                #endif

                #if DEBUG
                    tracker.logger = DefaultLogger(minLevel: .verbose)
                #endif

                debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")")
                debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)")
            }

            // MARK: - Methods

            func logScreen(name: String, path: String) {
                guard !tracker.isOptedOut else { return }
                guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
        
                let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS")
                tracker.track(
                    view: [name],
                    url: urlString
                )
            }

            func logEvent(
                name: String,
                action: String,
                category: String,
                params: [String: Any]?
            ) {
                guard !tracker.isOptedOut else { return }

                tracker.track(
                    eventWithCategory: category,
                    action: action,
                    name: name,
                    number: nil,
                    url: nil
                )
            }
        }

        // MARK: - Manager

        class AnalyticsManager {

            static var shared = AnalyticsManager()

            // MARK: - Properties

            var managers: [AnalyticsManagerProtocol] = []

            private var isEnabled: Bool = true

            // MARK: - Methods

            func setAnalyticsEnabled(_ enable: Bool) { 
                isEnabled = enable
            }

            func configure(siteId: String, url: String) {
                managers.append(
                    MatomoAnalyticsManager(
                        siteId: siteId,
                        url: url
                    )
                )
            }

            private func logScreen(name: String, path: String) {
                guard isEnabled else { return }

                managers.forEach { manager in
                    manager.logScreen(name: name, path: path)
                }
            }

            private func logEvent(
                name: String,
                action: String,
                category: String,
                params: [String: Any]?
            ) {
                guard isEnabled else { return }

                managers.forEach { manager in
                    manager.logEvent(
                        name: name,
                        action: action,
                        category: category,
                        params: params
                    )
                }
            }

            // MARK: - section_one

            func logScreenS1DefOne() {
                logScreen(
                    name: "s1 def one",
                    path: "s1_def_one/"
                )
            }

            func logEventS1DefTwo() {
                logEvent(
                    name: "s1 def two",
                    action: "test",
                    category: "test",
                    params: [:]
                )
            }

            // MARK: - section_two

            func logScreenS2DefOne() {
                logScreen(
                    name: "s2 def one",
                    path: "s2_def_one/"
                )
            }
        }

        """
        
        if extensionContent != expect {
            print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
        }
        XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
    }
    
    func testGeneratedExtensionContentMatomoAndFirebase() {
        // Given
        let sectionOne = AnalyticsCategory(id: "section_one")
        sectionOne.definitions = [
            getAnalyticsDefinition(id: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: .screen, tags: ["ios", "iosonly"]),
            getAnalyticsDefinition(id: "s1_def_two", action: "test", category: "test", name: "s1 def two", type: .event, tags: ["ios", "iosonly"]),
        ]
        
        let sectionTwo = AnalyticsCategory(id: "section_two")
        sectionTwo.definitions = [
            getAnalyticsDefinition(id: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: .screen, tags: ["ios","iosonly"]),
            getAnalyticsDefinition(id: "s2_def_two", action: "test", category: "test", name: "s2 def two", type: .event, tags: ["droid","droidonly"]),
        ]
        
        let sectionThree = AnalyticsCategory(id: "section_three")
        sectionThree.definitions = [
            getAnalyticsDefinition(id: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: .screen, tags: ["droid","droidonly"]),
            getAnalyticsDefinition(id: "s3_def_two", action: "test", category: "test", name: "s3 def two", type: .event, tags: ["droid","droidonly"]),
        ]
        
        // When
        let extensionContent = AnalyticsGenerator.getExtensionContent(
            targets: [TrackerType.matomo, TrackerType.firebase],
            sections: [sectionOne, sectionTwo, sectionThree],
            tags: ["ios", "iosonly"],
            staticVar: false,
            extensionName: "GenAnalytics"
        )

        // Expect Analytics
        let expect = """
        // Generated by ResgenSwift.Analytics \(ResgenSwiftVersion)

        import MatomoTracker
        import FirebaseAnalytics

        // MARK: - Protocol

        protocol AnalyticsManagerProtocol {

            func logScreen(name: String, path: String)
            func logEvent(
                name: String,
                action: String,
                category: String,
                params: [String: Any]?
            )
        }

        // MARK: - Matomo

        class MatomoAnalyticsManager: AnalyticsManagerProtocol {

            // MARK: - Properties

            private var tracker: MatomoTracker

            // MARK: - Init

            init(siteId: String, url: String) {
                debugPrint("[Matomo service] Server URL: \\(url)")
                debugPrint("[Matomo service] Site ID: \\(siteId)")
                tracker = MatomoTracker(
                    siteId: siteId,
                    baseURL: URL(string: url)!
                )

                #if DEBUG
                    tracker.dispatchInterval = 5
                #endif

                #if DEBUG
                    tracker.logger = DefaultLogger(minLevel: .verbose)
                #endif

                debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")")
                debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)")
            }

            // MARK: - Methods

            func logScreen(name: String, path: String) {
                guard !tracker.isOptedOut else { return }
                guard let trackerUrl = tracker.contentBase?.absoluteString else { return }

                let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS")
                tracker.track(
                    view: [name],
                    url: urlString
                )
            }

            func logEvent(
                name: String,
                action: String,
                category: String,
                params: [String: Any]?
            ) {
                guard !tracker.isOptedOut else { return }

                tracker.track(
                    eventWithCategory: category,
                    action: action,
                    name: name,
                    number: nil,
                    url: nil
                )
            }
        }

        // MARK: - Firebase

        class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
            func logScreen(name: String, path: String) {
                var parameters = [
                    AnalyticsParameterScreenName: name as NSObject
                ]

                Analytics.logEvent(
                    AnalyticsEventScreenView,
                    parameters: parameters
                )
            }

            func logEvent(
                name: String,
                action: String,
                category: String,
                params: [String: Any]?
            ) {
                var parameters: [String:NSObject] = [
                    "action": action as NSObject,
                    "category": category as NSObject,
                ]

                if let supplementaryParameters = params {
                    for (newKey, newValue) in supplementaryParameters {
                        if parameters.contains(where: { (key: String, value: NSObject) in
                            key == newKey
                        }) {
                            continue
                        }

                        parameters[newKey] = newValue as? NSObject
                    }
                }

                Analytics.logEvent(
                    name.replacingOccurrences(of: [" "], with: "_"),
                    parameters: parameters
                )
            }
        }

        // MARK: - Manager

        class AnalyticsManager {

            static var shared = AnalyticsManager()

            // MARK: - Properties

            var managers: [AnalyticsManagerProtocol] = []

            private var isEnabled: Bool = true

            // MARK: - Methods

            func setAnalyticsEnabled(_ enable: Bool) {
                isEnabled = enable
            }

            func configure(siteId: String, url: String) {
                managers.append(
                    MatomoAnalyticsManager(
                        siteId: siteId,
                        url: url
                    )
                )
                managers.append(FirebaseAnalyticsManager())
            }

            private func logScreen(name: String, path: String) {
                guard isEnabled else { return }
        
                managers.forEach { manager in
                    manager.logScreen(name: name, path: path)
                }
            }

            private func logEvent(
                name: String,
                action: String,
                category: String,
                params: [String: Any]?
            ) {
                guard isEnabled else { return }

                managers.forEach { manager in
                    manager.logEvent(
                        name: name,
                        action: action,
                        category: category,
                        params: params
                    )
                }
            }

            // MARK: - section_one

            func logScreenS1DefOne() {
                logScreen(
                    name: "s1 def one",
                    path: "s1_def_one/"
                )
            }

            func logEventS1DefTwo() {
                logEvent(
                    name: "s1 def two",
                    action: "test",
                    category: "test",
                    params: [:]
                )
            }

            // MARK: - section_two

            func logScreenS2DefOne() {
                logScreen(
                    name: "s2 def one",
                    path: "s2_def_one/"
                )
            }
        }

        """
        
        if extensionContent != expect {
            print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
        }
        XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
    }
}
