//
//  AnalyticsFileParser.swift
//
//
//  Created by Loris Perret on 06/12/2023.
//

import Foundation
import Yams

class AnalyticsFileParser {

    // MARK: - Properties

    private var inputFile: String = ""
    private var target: String = ""

    // MARK: - Methods

    func parse(_ inputFile: String, target: String) -> [AnalyticsCategory] {
        self.inputFile = inputFile
        self.target = target

        let tagFile = parseYaml()

        return tagFile
            .categories
            .map { categorie in
                let section = AnalyticsCategory(id: categorie.id)

                if let screens = categorie.screens {
                    section
                        .definitions
                        .append(
                            contentsOf: getTagDefinitionScreen(from: screens)
                        )
                }

                if let events = categorie.events {
                    section
                        .definitions
                        .append(
                            contentsOf: getTagDefinitionEvent(from: events)
                        )
                }

                return section
            }
    }

    // MARK: - Private methods

    private func parseYaml() -> AnalyticsFile {
        guard let data = FileManager().contents(atPath: inputFile) else {
            let error = AnalyticsError.fileNotExists(inputFile)
            print(error.description)
            Analytics.exit(withError: error)
        }

        do {
            let tagFile = try YAMLDecoder().decode(AnalyticsFile.self, from: data)
            return tagFile
        } catch {
            let error = AnalyticsError.parseFailed(error.localizedDescription)
            print(error.description)
            Analytics.exit(withError: error)
        }
    }

    private static func getParameters(from parameters: [AnalyticsParameterDTO]) -> [AnalyticsParameter] {
        func verify(value: String?, for type: ParameterType) {
            guard let value, value.isEmpty == false else { return }

            switch type {
            case .int:
                if Int(value) == nil {
                    let error = AnalyticsError.invalidParameter("type of \(value) is not \(type)")
                    print(error.description)
                    Analytics.exit(withError: error)
                }

            case .bool:
                if Bool(value.lowercased()) == nil {
                    let error = AnalyticsError.invalidParameter("type of \(value) is not \(type)")
                    print(error.description)
                    Analytics.exit(withError: error)
                }

            case .double:
                if Double(value) == nil {
                    let error = AnalyticsError.invalidParameter("type of \(value) is not \(type)")
                    print(error.description)
                    Analytics.exit(withError: error)
                }

            case .string:
                break
            }
        }

        return parameters.map { dtoParameter in
            // Type
            let type = dtoParameter.type.uppercasedFirst()

            guard let typeEnum = ParameterType(rawValue: type) else {
                let error = AnalyticsError.invalidParameter("type of \(dtoParameter.name)")
                print(error.description)
                Analytics.exit(withError: error)
            }

            if dtoParameter.value != nil, dtoParameter.replaceIn != nil {
                let error = AnalyticsError.invalidParameter("you can't set 'value' and 'replaceIn' for \(dtoParameter.name)")
                print(error.description)
                Analytics.exit(withError: error)
            }

            verify(value: dtoParameter.value, for: typeEnum)
            verify(value: dtoParameter.defaultValue, for: typeEnum)

            let parameter = AnalyticsParameter(
                name: dtoParameter.name,
                type: typeEnum,
                value: dtoParameter.value ?? "",
                defaultValue: dtoParameter.defaultValue ?? ""
            )

            if let replaceIn = dtoParameter.replaceIn {
                parameter.replaceIn = replaceIn.components(separatedBy: ",")
            }

            return parameter
        }
    }

    private func getTagDefinition(
        id: String,
        name: String,
        type: AnalyticsDefinition.TagType,
        tags: String,
        comments: String?,
        parameters: [AnalyticsParameterDTO]?
    ) -> AnalyticsDefinition {
        let definition = AnalyticsDefinition(id: id, name: name, type: type)
        definition.tags = tags
            .components(separatedBy: ",")
            .map { $0.removeLeadingTrailingWhitespace() }

        if let comments {
            definition.comments = comments
        }

        if let parameters {
            definition.parameters = Self.getParameters(from: parameters)
        }

        return definition
    }

    private func getTagDefinitionScreen(from screens: [AnalyticsDefinitionScreenDTO]) -> [AnalyticsDefinition] {
        screens.map { screen in
            let definition: AnalyticsDefinition = getTagDefinition(
                id: screen.id,
                name: screen.name,
                type: .screen,
                tags: screen.tags,
                comments: screen.comments,
                parameters: screen.parameters
            )

            if target.contains(TrackerType.matomo.value) {
                // Path

                guard let path = screen.path else {
                    let error = AnalyticsError.missingElement("screen path")
                    print(error.description)
                    Analytics.exit(withError: error)
                }

                definition.path = path
            } else if let path = screen.path {
                definition.path = path
            }

            return definition
        }
    }

    private func getTagDefinitionEvent(from events: [AnalyticsDefinitionEventDTO]) -> [AnalyticsDefinition] {
        events.map { event in
            let definition: AnalyticsDefinition = getTagDefinition(
                id: event.id,
                name: event.name,
                type: .event,
                tags: event.tags,
                comments: event.comments,
                parameters: event.parameters
            )

            if target.contains(TrackerType.matomo.value) {
                // Category
                guard let category = event.category else {
                    let error = AnalyticsError.missingElement("event category")
                    print(error.description)
                    Analytics.exit(withError: error)
                }

                definition.category = category

                // Action
                guard let action = event.action else {
                    let error = AnalyticsError.missingElement("event action")
                    print(error.description)
                    Analytics.exit(withError: error)
                }

                definition.action = action
            } else {
                if let category = event.category {
                    definition.category = category
                }

                if let action = event.action {
                    definition.action = action
                }
            }

            return definition
        }
    }
}
