//
//  ParsingHelpers.swift
//  lichunWebsocket
//
//  Helper functions for parsing WebSocket data
//

import Foundation

// MARK: - Activity Parsing
func parseActivity(from activityData: [String: Any]) -> (any ActivityProtocol)? {
    guard let activityType = activityData["type"] as? String else {
        return nil
    }

    switch activityType {
    case "high_school":
        return HighSchoolClass(
            id: activityData["id"] as? String ?? "",
            title: activityData["title"] as? String ?? "",
            image: activityData["image"] as? String ?? "",
            type: activityData["type"] as? String ?? "",
            description: activityData["description"] as? String ?? ""
        )
    case "elementary_school":
        return ElementarySchoolClass(
            id: activityData["id"] as? String ?? "",
            title: activityData["title"] as? String ?? "",
            image: activityData["image"] as? String ?? "",
            type: activityData["type"] as? String ?? "",
            description: activityData["description"] as? String ?? ""
        )
    case "college", "college_major":
        // College uses same structure as HighSchoolClass for display purposes
        return HighSchoolClass(
            id: activityData["id"] as? String ?? "",
            title: activityData["title"] as? String ?? "",
            image: activityData["image"] as? String ?? "",
            type: activityData["type"] as? String ?? "",
            description: activityData["description"] as? String ?? ""
        )
    case "job":
        return OccupationClass(
            id: activityData["id"] as? String ?? "",
            title: activityData["title"] as? String ?? "",
            image: activityData["image"] as? String ?? "",
            type: activityData["type"] as? String ?? "",
            requirements: activityData["requirements"] as? String ?? "",
            description: activityData["description"] as? String ?? "",
            hourType: activityData["hourType"] as? String ?? "",
            levels: activityData["levels"] as? [jobLevel] ?? []
        )
    case "extracurricular":
        return ExtracurricularClass(
            id: activityData["id"] as? String ?? "",
            title: activityData["title"] as? String ?? "",
            image: activityData["image"] as? String ?? "",
            type: activityData["type"] as? String ?? "",
            description: activityData["description"] as? String ?? ""
        )
    default:
        return nil
    }
}

// MARK: - Person Parsing
func parsePerson(from personData: [String: Any]) -> Person {
    let person = Person()
    person.id = personData["id"] as? String ?? ""
    person.firstname = personData["firstname"] as? String ?? ""
    person.lastname = personData["lastname"] as? String ?? ""
    person.status = personData["status"] as? String ?? ""
    person.description = personData["description"] as? String ?? ""
    person.ageYears = personData["ageYears"] as? Int ?? 0
    person.birthday = personData["birthday"] as? String ?? ""
    person.affinity = personData["affinity"] as? Int ?? 0
    person.mood = personData["mood"] as? String ?? ""
    person.image = personData["image"] as? String ?? ""
    person.relationship = personData["relationship"] as? String ?? ""
    person.education = personData["education"] as? String ?? ""
    person.occupation = personData["occupation"] as? String ?? ""
    person.location = personData["location"] as? String ?? ""
    // Backend may send health as Int or Double (e.g. DB-decoded numeric types).
    // If the field is ABSENT, leave the model default (100) intact. Previously
    // this clobbered health to 0 whenever a payload omitted the field, which
    // made a character display an alarming "Health 0" even while alive.
    if let health = personData["health"] as? Int {
        person.health = health
    } else if let health = personData["health"] as? Double {
        person.health = Int(health)
    }
    person.calcEnergy = personData["calcEnergy"] as? Int ?? 0
    person.money = personData["money"] as? Int ?? 0
    person.diamonds = personData["diamonds"] as? Int ?? 0
    person.prestige = personData["prestige"] as? Int ?? 0
    person.happiness = personData["happiness"] as? Int ?? 0
    person.intraDayMessage = personData["intraDayMessage"] as? String ?? ""
    person.likes = personData["likes"] as? [String] ?? []
    person.dislikes = personData["dislikes"] as? [String] ?? []

    // Parse enhanced profile properties
    person.bio = personData["bio"] as? String ?? ""
    person.intelligence = personData["intelligence"] as? Int ?? 50
    person.compatibilityScore = personData["compatibilityScore"] as? Int ?? Int.random(in: 40...95)
    person.interests = personData["interests"] as? [String] ?? []
    person.personalityTraits = personData["personalityTraits"] as? [String] ?? []

    // Parse activities properly
    if let activitiesData = personData["activities"] as? [[String: Any]] {
        person.activities = activitiesData.compactMap { parseActivity(from: $0) }
    } else {
        person.activities = []
    }

    // Parse habits properly
    if let habitsData = personData["habits"] as? [[String: Any]] {
        do {
            let jsonData = try JSONSerialization.data(withJSONObject: habitsData)
            let decoder = JSONDecoder()
            person.habits = try decoder.decode([Habit].self, from: jsonData)
        } catch {
            print("Failed to decode habits: \(error)")
            person.habits = []
        }
    } else {
        person.habits = []
    }

    // Parse activity records properly
    if let recordsData = personData["activityRecords"] as? [[String: Any]] {
        let allRecords = recordsData.compactMap { parseActivityRecord(from: $0) }
        // Memory optimization: Limit to 50 most recent activity records per person
        person.activityRecords = allRecords.count > 50 ? Array(allRecords.suffix(50)) : allRecords
    } else {
        person.activityRecords = []
    }

    // Parse available conversations properly
    if let conversationsData = personData["availableConversations"] as? [[String: Any]] {
        person.availableConversations = conversationsData.compactMap { data in
            guard let fname = data["fname"] as? String,
                  let button = data["button"] as? String else {
                return nil
            }
            return ConversationClass(fname: fname, button: button)
        }
    } else {
        person.availableConversations = []
    }

    person.relationships = personData["relationships"] as? [String] ?? []

    // Parse items properly
    if let itemsData = personData["items"] as? [[String: Any]] {
        do {
            let jsonData = try JSONSerialization.data(withJSONObject: itemsData)
            let decoder = JSONDecoder()
            person.items = try decoder.decode([StoreItem].self, from: jsonData)
        } catch {
            print("Failed to decode items: \(error)")
            person.items = []
        }
    } else {
        person.items = []
    }

    person.lastUpdatedTime = Date()
    let educationData = personData["current_education"] as? [String: Any]
    person.currentEducation = parseEducationRecord(from: educationData)
    return person
}

// MARK: - Retrieve Person
func retrievePerson(personID: String?, webSocketService: WebSocketService) {
    if let personID = personID {
        let message = WebSocketCommands.retrievePerson(personID)
        webSocketService.personReceived = false
        webSocketService.sendMessage(message: message)
    } else {
        // Handle the case where personID is nil
        print("Error: personID is nil")
    }
}

// MARK: - Level Parsing
func parseLevel(from data: [String: Any]?) -> jobLevel? {
    guard let data = data else { return nil }
    let id = data["id"] as? String ?? ""
    let level = data["level"] as? String ?? ""
    let salary = data["salary"] as? Int ?? 0
    let energy_modifier = data["energy_modifier"] as? Int ?? 0
    return jobLevel(id: id, level: level, salary: salary, energy_modifier: energy_modifier)
}

// MARK: - Activity Record Parsing
func parseActivityRecord(from data: [String: Any]?) -> ActivityRecord? {
    guard let data = data else {
        return nil
    }

    // Parse the common properties from `data` dictionary
    let id = data["id"] as? String ?? ""
    let type = data["type"] as? String ?? ""
    let focus = data["focus"] as? String ?? ""
    // Support both "location" and "locationId" (TypeScript backend uses locationId)
    let location = data["location"] as? String ?? data["locationId"] as? String ?? ""
    // Support both "dateStarted" and "date" (TypeScript backend uses date)
    let dateStarted = data["dateStarted"] as? Date ?? data["date"] as? Date
    let achievements = data["achievements"] as? [String] ?? []
    let performance = data["performance"] as? Int ?? 50
    let levelData = data["level"] as? [String: Any]
    let level: jobLevel? = parseLevel(from: levelData)

    let activityRecord = ActivityRecord(id: id, type: type, location: location, dateStarted: dateStarted ?? Date(), focus:focus,level:level ?? nil, performance:performance)
    achievements.forEach {
        activityRecord.addAchievement($0)
    }

    // Check for GPA - support both "GPA" (uppercase) and "gpa" (lowercase from TypeScript)
    // Also support Int values that need to be converted to Double
    var gpaDouble: Double? = nil
    if let gpa = data["GPA"] as? Double {
        gpaDouble = gpa
    } else if let gpa = data["gpa"] as? Double {
        gpaDouble = gpa
    } else if let gpa = data["GPA"] as? Int {
        gpaDouble = Double(gpa)
    } else if let gpa = data["gpa"] as? Int {
        gpaDouble = Double(gpa)
    }

    if let GPA = gpaDouble {
        // If the data dictionary contains GPA, assume this is an EducationRecord
        let major = data["major"] as? String
        let minor = data["minor"] as? String
        let educationLevel = data["educationLevel"] as? String ?? ""

        let educationRecord = EducationRecord(id: id, educationLevel: educationLevel, location: location, dateStarted: dateStarted ?? Date(), focus: focus, GPA: GPA, level: level, performance:performance)

        educationRecord.major = major
        educationRecord.minor = minor
        educationRecord.GPA = GPA
        achievements.forEach {
            educationRecord.addAchievement($0)
        }

        return educationRecord
    }

    return activityRecord
}

// MARK: - Education Record Parsing
func parseEducationRecord(from data: [String: Any]?) -> EducationRecord? {
    guard let data = data else {
        return nil
    }

    // Parse the education record properties from `data` dictionary
    let id = data["id"] as? String ?? ""
    let focus = data["focus"] as? String ?? ""
    let educationLevel = data["educationLevel"] as? String ?? ""
    // Support both "location" and "locationId" (TypeScript backend uses locationId)
    let location = data["location"] as? String ?? data["locationId"] as? String ?? ""
    // Support both "dateStarted" and "date" (TypeScript backend uses date)
    let dateStarted = data["dateStarted"] as? Date ?? data["date"] as? Date
    let major = data["major"] as? String
    let minor = data["minor"] as? String
    // Support both "GPA" (uppercase) and "gpa" (lowercase from TypeScript)
    // Also support Int values that need to be converted to Double
    var gpaValue: Double = 75.0
    if let gpa = data["GPA"] as? Double {
        gpaValue = gpa
    } else if let gpa = data["gpa"] as? Double {
        gpaValue = gpa
    } else if let gpa = data["GPA"] as? Int {
        gpaValue = Double(gpa)
    } else if let gpa = data["gpa"] as? Int {
        gpaValue = Double(gpa)
    }
    let achievements = data["achievements"] as? [String] ?? []
    let performance = data["performance"] as? Int ?? 0
    let levelData = data["level"] as? [String: Any]
    let level: jobLevel? = levelData != nil ? parseLevel(from: levelData!) : nil

    let educationRecord = EducationRecord(id: id, educationLevel: educationLevel, location: location, dateStarted: dateStarted ?? Date(), focus: focus, GPA: gpaValue, level:level, performance:performance)

    educationRecord.major = major
    educationRecord.minor = minor
    educationRecord.GPA = gpaValue

    achievements.forEach {
        educationRecord.addAchievement($0)
    }

    return educationRecord
}

// MARK: - GPA Conversion
/// Convert the backend's 0-100 GPA metric to the 0.0-4.0 scale shown in the UI.
///
/// The backend stores GPA on a 0-100 scale (default 50 = average student) and
/// converts to the 4.0 scale linearly via `(GPA / 100) * 4` (see the server's
/// `EducationRecord.getGPA4Scale`). This mirrors that exactly so the client and
/// server agree.
///
/// The previous implementation used a letter-grade lookup table that jumped from
/// `65 -> 1.3` straight to `0 -> 0.0`, so ANY GPA from 1-64 (including the 50 a
/// brand-new enrollment carries) wrongly displayed as "GPA 0.0".
func GPpercentToGPA(GPPercent: Int) -> Double {
    let scaled = (Double(GPPercent) / 100.0) * 4.0
    let clamped = max(0.0, min(4.0, scaled))
    return (clamped * 10).rounded() / 10  // one decimal place
}

// MARK: - Extensions
extension Double {
    func rounded(toPlaces places: Int) -> Double {
        let divisor = pow(10.0, Double(places))
        return (self * divisor).rounded() / divisor
    }
}
