//
//  Context+Calculations.swift
//  LeCountdown
//
//  Created by Laurent Morvillier on 20/02/2023.
//

import Foundation
import CoreData

extension NSManagedObjectContext {
    
    func compute(activity: Activity, stats: [Stat], filter: Filter?) throws -> [StatValue] {
        
        // Step 1:
        // - Create the summing expression on the amount attribute.
        // - Name the expression result as 'amountTotal'.
        // - Assign the expression result data type as a Double.
        
        var expressions: [NSExpressionDescription] = []
        
        for stat in stats {
            let expression = NSExpressionDescription()
            expression.expression = NSExpression(forFunction: stat.function, arguments:[NSExpression(forKeyPath: stat.field)])
            expression.name = stat.stringIdentifier;
            expression.expressionResultType = NSAttributeType.doubleAttributeType
            
            expressions.append(expression)
        }
        
        // Step 2:
        // - Create the fetch request for the Movement entity.
        // - Indicate that the fetched properties are those that were
        //   described in `expression`.
        // - Indicate that the result type is a dictionary.
        
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Record")
        
        let basePredicate = NSPredicate(format: "start != nil AND activity = %@", activity)
        var predicates: [NSPredicate] = []
        predicates.append(basePredicate)
        if let filter {
            predicates.append(filter.predicate)
        }
        let finalPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
        
        fetchRequest.predicate = finalPredicate
        fetchRequest.propertiesToFetch = expressions
        fetchRequest.resultType = NSFetchRequestResultType.dictionaryResultType
        
        // Step 3:
        // - Execute the fetch request which returns an array.
        // - There will only be one result. Get the first array
        //   element and assign to 'resultMap'.
        // - The summed amount value is in the dictionary as
        //   'amountTotal'. This will be summed value.
        
        var statValues: [StatValue] = []
        
        let results = try self.fetch(fetchRequest)
        if let resultMap = results.first as? [String : Any] {
            
            for stat in stats {
                
                if let result = resultMap[stat.stringIdentifier] {
                    
                    var value: NSDecimalNumber? = nil
                    switch result {
                    case let double as Double:
                        value = NSDecimalNumber(value: double)
                    case let integer as Int:
                        value = NSDecimalNumber(integerLiteral: integer)
                    default:
                        print("unmanaged value type = \(String(describing: result))")
                        break
                    }
                    
                    if let value {
                        let request = Record.fetchRequest()
                        request.predicate = finalPredicate
                        request.sortDescriptors = [NSSortDescriptor(key: "start", ascending: true)]

                        let records: [Record] = try self.fetch(request)
                        
                        let points: [Point] = records.compactMap { $0.point(stat:stat) }
                        
                        let sv = StatValue(stat: stat, value: value, points: points)
                        statValues.append(sv)
                    }
                }
            }
        }
        return statValues
    }
}

fileprivate extension Record {
    
    func point(stat: Stat) -> Point? {
        if let start {
            switch stat {
            case .count:
                return Point(date: start, value: NSNumber(value: 1))
            case .totalDuration, .averageDuration:
                return Point(date: start, value: NSNumber(value: self.duration / 3600.0))
            }
        }
        return nil
    }
    
}
