import Foundation
import Combine

/// Manages game events: questions, message events, life events, tutorials,
/// relationship events, and the EventV2 envelope system.
class EventService: ObservableObject {
    @Published var questionQueue: [Question] = []
    @Published var currentQuestion: Question?
    @Published var currentMessageEvent: MessageEvent?
    @Published var currentRelationshipEvent: RelationshipEvent?
    @Published var lifeEvents: [MessageEvent] = []
    @Published var unclaimedEventCount: Int = 0

    // Tutorial state
    @Published var tutorialStep: Int = 0
    @Published var tutorialComplete: Bool = false
    @Published var tutorialMessage: String?

    weak var facade: WebSocketService?

    func sendMessage(_ message: [String: Any]) {
        facade?.sendMessage(message: message)
    }

    // MARK: - Actions

    func sendAnswer(answer: AnswerOption, questionID: String) {
        guard let choiceId = answer.choiceId else {
            facade?.currentError = .invalidOperation(message: "Invalid choice. Please try again.")
            return
        }
        sendEventResponse(eventId: questionID, choiceId: choiceId)
    }

    func sendEventResponse(eventId: String, choiceId: String) {
        sendMessage([
            "type": "eventResponse",
            "message": [
                "eventId": eventId,
                "choiceId": choiceId
            ]
        ])
    }

    func removeQuestion(eventId: String) {
        questionQueue.removeAll { $0.eventId == eventId }
        currentQuestion = questionQueue.first
    }

    func claimEvent(_ event: MessageEvent) {
        if let index = lifeEvents.firstIndex(where: { $0.id == event.id }) {
            lifeEvents[index].claimed = true
            lifeEvents[index].claimedAt = Date()
            updateUnclaimedCount()
        }

        sendMessage([
            "type": "claimEvent",
            "message": [
                "eventId": event.id,
                "timestamp": ISO8601DateFormatter().string(from: Date())
            ]
        ])
    }

    func updateUnclaimedCount() {
        unclaimedEventCount = lifeEvents.filter { $0.isClaimable && !$0.claimed }.count
    }

    func determineCategory(from messageDict: [String: Any]) -> EventCategory {
        let message = (messageDict["message"] as? String ?? "").lowercased()

        if message.contains("job") || message.contains("promot") || message.contains("hired") || message.contains("work") {
            return .career
        } else if message.contains("date") || message.contains("friend") || message.contains("relationship") {
            return .social
        } else if message.contains("achievement") || message.contains("unlock") || message.contains("award") {
            return .achievement
        } else if message.contains("graduat") || message.contains("school") || message.contains("college") {
            return .education
        } else if message.contains("health") || message.contains("sick") || message.contains("hospital") {
            return .health
        } else if message.contains("money") || message.contains("dollar") || message.contains("paid") {
            return .finance
        } else if message.contains("lost") || message.contains("fired") || message.contains("broke") {
            return .negative
        } else {
            return .neutral
        }
    }

    // MARK: - EventV2 Result Mapping

    /// Maps resolved effects (stats + non-cost resource gains) into integer
    /// deltas for display. Resources that are pure costs (energy/money/diamonds)
    /// are already shown by the cost section, so only positive resource gains
    /// are surfaced here to avoid double-counting costs.
    static func statDeltas(from effects: EventV2Effects?) -> [String: Int] {
        guard let effects else { return [:] }
        var deltas: [String: Int] = [:]

        if let stats = effects.stats {
            for (key, value) in stats {
                let rounded = Int(value.rounded())
                if rounded != 0 {
                    deltas[key] = rounded
                }
            }
        }

        if let resources = effects.resources {
            // Only surface positive resource gains as deltas; costs are shown
            // separately as the negative cost pills on the confirmation card.
            if let energy = resources.energy, energy > 0 { deltas["energy"] = Int(energy.rounded()) }
            if let money = resources.money, money > 0 { deltas["money"] = Int(money.rounded()) }
            if let diamonds = resources.diamonds, diamonds > 0 { deltas["diamonds"] = Int(diamonds.rounded()) }
        }

        return deltas
    }

    /// Maps named affinity changes into the display model.
    static func affinityChanges(from changes: [EventV2RelationshipChange]?) -> [AffinityChange] {
        guard let changes else { return [] }
        return changes.compactMap { change in
            let delta = Int(change.affinityDelta.rounded())
            guard delta != 0 else { return nil }
            return AffinityChange(personId: change.personId, name: change.name, delta: delta)
        }
    }

    // MARK: - EventV2 Envelope

    func decodeEventV2Envelope(from payload: [String: Any]) -> EventV2Envelope? {
        guard let data = try? JSONSerialization.data(withJSONObject: payload, options: []) else {
            return nil
        }
        return try? JSONDecoder().decode(EventV2Envelope.self, from: data)
    }

    func handleEventV2Envelope(_ envelope: EventV2Envelope) {
        guard let facade = facade else { return }

        switch envelope {
        case .prompt(let prompt):
            let answers = prompt.choices.map { choice in
                AnswerOption(
                    option: choice.text,
                    id: choice.choiceId,
                    data: nil,
                    energyCost: choice.energyCost,
                    moneyCost: choice.moneyCost,
                    diamondCost: choice.diamondCost
                )
            }
            let question = Question(
                id: prompt.eventId,
                question: prompt.prompt,
                answers: answers,
                characters: nil,
                image: nil
            )

            if !questionQueue.contains(where: { $0.id == question.id }) {
                questionQueue.append(question)
            }
            if currentQuestion == nil {
                currentQuestion = questionQueue.first
            }

        case .resolved(let resolved):
            removeQuestion(eventId: resolved.eventId)

            let categoryKey = resolved.metadata?.category
            let resolvedStatus = resolved.metadata?.status ?? "resolved"

            // Concrete changes surfaced on the confirmation screen.
            let statDeltas = Self.statDeltas(from: resolved.effects)
            let affinityChanges = Self.affinityChanges(from: resolved.resolvedRelationships)

            var messageEvent = MessageEvent(
                id: resolved.instanceId,
                message: resolved.resolutionText,
                type: resolved.type,
                date: facade.player.date,
                hour: String(facade.player.hourOfDay),
                energyCost: nil,
                diamondCost: nil,
                moneyCost: nil,
                affinityChange: nil,
                title: "Event Resolved",
                image: nil,
                category: EventCategory.fromV2Category(categoryKey),
                status: resolvedStatus,
                categoryKey: categoryKey,
                outcomeText: resolved.resolutionText,
                statDeltas: statDeltas.isEmpty ? nil : statDeltas,
                affinityChanges: affinityChanges.isEmpty ? nil : affinityChanges
            )
            messageEvent.claimed = true
            messageEvent.claimedAt = Date()
            lifeEvents.insert(messageEvent, at: 0)
            if lifeEvents.count > 50 {
                lifeEvents.removeLast()
            }
            updateUnclaimedCount()

            // If the player is still looking at the optimistic "Your Decision"
            // confirmation card for this event, enrich it in place with the
            // real outcome narrative + concrete stat/affinity changes so the
            // screen answers "what happened?" rather than vanishing silently.
            if var pending = currentMessageEvent,
               pending.id == "result_\(resolved.eventId)" {
                pending.outcomeText = resolved.resolutionText
                if !statDeltas.isEmpty { pending.statDeltas = statDeltas }
                if !affinityChanges.isEmpty { pending.affinityChanges = affinityChanges }
                currentMessageEvent = pending
            }

        case .error(let eventError):
            facade.currentError = .serverError(message: eventError.message)
        }
    }

    // MARK: - Message Handling

    func handleMessage(type: String, data: [String: Any]) -> Bool {
        switch type {
        case "messageEvent":
            handleMessageEvent(data)
            return true
        case "questionEvent":
            handleQuestionEvent(data)
            return true
        case "relationshipEvent":
            handleRelationshipEvent(data)
            return true
        case "error":
            handleErrorMessage(data)
            return true
        case "tutorialStepUpdated", "onboardingComplete", "tutorial_message":
            handleTutorialMessage(data)
            return true
        default:
            return false
        }
    }

    // MARK: - Private Handlers

    private func handleMessageEvent(_ data: [String: Any]) {
        let messageDict = data
        let categoryKey = messageDict["category"] as? String
        var messageEvent = MessageEvent(
            id: messageDict["id"] as? String ?? UUID().uuidString,
            message: messageDict["message"] as? String ?? "",
            type: messageDict["type"] as? String ?? "messageEvent",
            date: messageDict["date"] as? String,
            hour: messageDict["hour"] as? String,
            energyCost: messageDict["energyCost"] as? Int,
            diamondCost: messageDict["diamondCost"] as? Int,
            moneyCost: messageDict["moneyCost"] as? Int,
            affinityChange: messageDict["affinityChange"] as? Int,
            title: messageDict["title"] as? String,
            image: messageDict["image"] as? String,
            status: messageDict["status"] as? String,
            categoryKey: categoryKey
        )

        if let categoryKey, !categoryKey.isEmpty {
            messageEvent.category = EventCategory.fromV2Category(categoryKey)
        } else {
            messageEvent.category = self.determineCategory(from: messageDict)
        }

        if messageEvent.isNegative || !messageEvent.isClaimable {
            messageEvent.claimed = true
            messageEvent.claimedAt = Date()
        }

        if let image = messageEvent.image, !image.isEmpty {
            self.currentMessageEvent = messageEvent
        } else {
            self.lifeEvents.insert(messageEvent, at: 0)
            self.updateUnclaimedCount()

            if self.lifeEvents.count > 50 {
                self.lifeEvents.removeLast()
            }
        }
    }

    private func handleQuestionEvent(_ data: [String: Any]) {
        let questionID = data["id"] as? String ?? ""
        let questionString = data["message"] as? String ?? ""
        var answersArray: [AnswerOption] = []
        if let answersAny = data["answers"] {
            if let answersData = answersAny as? [[String: Any]] {
                let jsonData = try? JSONSerialization.data(withJSONObject: answersData)
                let decoder = JSONDecoder()
                if let d = jsonData, let decoded = try? decoder.decode([AnswerOption].self, from: d) {
                    answersArray = decoded
                }
            }
        }

        var charactersArray: [SimplePerson]? = nil
        if let charactersAny = data["characters"] {
            if let charactersData = charactersAny as? [[String: Any]] {
                let jsonData = try? JSONSerialization.data(withJSONObject: charactersData)
                let decoder = JSONDecoder()
                charactersArray = try? decoder.decode([SimplePerson].self, from: jsonData ?? Data())
            }
        }

        let imageString = data["image"] as? String

        let newQuestion = Question(id: questionID, question: questionString, answers: answersArray, characters: charactersArray, image: imageString)
        self.questionQueue.append(newQuestion)
        self.currentQuestion = self.questionQueue[0]
    }

    private func handleTutorialMessage(_ data: [String: Any]) {
        guard let messageType = data["type"] as? String else { return }

        switch messageType {
        case "tutorialStepUpdated":
            if let step = data["step"] as? Int {
                self.tutorialStep = step
            }

        case "onboardingComplete":
            self.tutorialComplete = true
            if let reward = data["reward"] as? Int {
                facade?.setRecentAchievementUnlock(Achievement(
                    id: "onboarding_complete",
                    name: "First Steps",
                    description: "Complete onboarding tutorial",
                    category: .education,
                    reward: reward,
                    requirement: "Complete onboarding",
                    unlocked: true,
                    unlockedAt: Date(),
                    progress: nil,
                    progressRequired: nil,
                    acknowledged: false
                ))
            }

        case "tutorial_message":
            if let message = data["message"] as? String {
                self.tutorialMessage = message
            }

        default:
            break
        }
    }

    private func handleErrorMessage(_ data: [String: Any]) {
        guard let errorCode = data["error_code"] as? String,
              let message = data["message"] as? String else { return }

        AnalyticsManager.shared.track(.serverError(errorCode: errorCode, message: message))

        let error = NSError(
            domain: "WebSocketService",
            code: -2,
            userInfo: [NSLocalizedDescriptionKey: "Server error: \(message)"]
        )
        AnalyticsManager.shared.recordError(error, additionalInfo: [
            "context": "server_error",
            "error_code": errorCode,
            "message": message
        ])

        switch errorCode {
        case "INSUFFICIENT_ENERGY", "INSUFFICIENT_MONEY", "INSUFFICIENT_DIAMONDS":
            let resource = errorCode.replacingOccurrences(of: "INSUFFICIENT_", with: "").lowercased()
            if let required = data["required"] as? Int,
               let available = data["available"] as? Int {
                facade?.currentError = .insufficientResources(resource: resource, required: required, available: available)
            }
        case "SERVER_ERROR":
            facade?.currentError = .serverError(message: message)
        case "INVALID_OPERATION":
            facade?.currentError = .invalidOperation(message: message)
        case "TIMEOUT":
            facade?.currentError = .timeout
        default:
            facade?.currentError = .serverError(message: message)
        }
    }

    private func handleRelationshipEvent(_ data: [String: Any]) {
        guard let eventId = data["id"] as? String,
              let eventTypeStr = data["eventType"] as? String,
              let eventType = RelationshipEvent.EventType(rawValue: eventTypeStr),
              let title = data["title"] as? String,
              let description = data["description"] as? String,
              let partnerName = data["partnerName"] as? String,
              let partnerId = data["partnerId"] as? String,
              let choicesData = data["choices"] as? [[String: Any]] else {
            return
        }

        let choices = choicesData.compactMap { choiceData -> EventChoice? in
            guard let id = choiceData["id"] as? String,
                  let text = choiceData["text"] as? String,
                  let affinityChange = choiceData["affinityChange"] as? Int else {
                return nil
            }

            return EventChoice(
                id: id,
                text: text,
                affinityChange: affinityChange,
                diamondCost: choiceData["diamondCost"] as? Int ?? 0,
                moneyCost: choiceData["moneyCost"] as? Int ?? 0,
                energyCost: choiceData["energyCost"] as? Int ?? 0
            )
        }

        self.currentRelationshipEvent = RelationshipEvent(
            id: eventId,
            type: eventType,
            title: title,
            description: description,
            partnerName: partnerName,
            partnerId: partnerId,
            choices: choices
        )
    }
}
