import Foundation
import ComposeApp
import Combine

/// ViewModel for task management.
/// Observes DataManagerObservable for cached data.
/// Calls APILayer directly for all operations.
@MainActor
class TaskViewModel: ObservableObject {
    // MARK: - Published Properties (from DataManager observation)
    @Published var tasksResponse: TaskColumnsResponse?

    /// When true, DataManager observation is paused to allow completion animation to play
    /// without the task being moved out of its column prematurely.
    var isAnimatingCompletion = false

    // MARK: - Local State
    @Published var actionState: ActionState<TaskActionType> = .idle
    @Published var errorMessage: String?
    @Published var completions: [TaskCompletionResponse] = []
    @Published var isLoadingCompletions: Bool = false
    @Published var completionsError: String?
    @Published var isLoadingTasks: Bool = false
    @Published var tasksError: String?

    /// The residence ID this kanban is filtered to (nil = all tasks)
    private(set) var currentResidenceId: Int32?

    // MARK: - Computed Properties (Backward Compatibility)

    var isLoading: Bool { actionState.isLoading }
    var taskCreated: Bool { actionState.isSuccess(.create) }
    var taskUpdated: Bool { actionState.isSuccess(.update) }
    var taskCancelled: Bool { actionState.isSuccess(.cancel) }
    var taskUncancelled: Bool { actionState.isSuccess(.uncancel) }
    var taskMarkedInProgress: Bool { actionState.isSuccess(.markInProgress) }
    var taskArchived: Bool { actionState.isSuccess(.archive) }
    var taskUnarchived: Bool { actionState.isSuccess(.unarchive) }

    // MARK: - Private Properties
    private var cancellables = Set<AnyCancellable>()
    private let dataManager: DataManagerObservable

    // MARK: - Initialization
    /// - Parameter dataManager: Observable cache the VM subscribes to.
    ///   Defaults to the shared singleton. Tests inject a fixture-backed
    ///   instance so populated-state snapshots render real data.
    init(dataManager: DataManagerObservable = .shared) {
        self.dataManager = dataManager

        // Seed from current cache so snapshot tests/previews render
        // populated state without waiting for Combine's async dispatch.
        self.tasksResponse = dataManager.allTasks

        // Observe injected DataManagerObservable for all tasks data
        dataManager.$allTasks
            .receive(on: DispatchQueue.main)
            .sink { [weak self] allTasks in
                // Skip DataManager updates during completion animation to prevent
                // the task from being moved out of its column before the animation finishes
                guard self?.isAnimatingCompletion != true else { return }
                // Only update if we're showing all tasks (no residence filter)
                if self?.currentResidenceId == nil {
                    self?.tasksResponse = allTasks
                    if allTasks != nil {
                        self?.isLoadingTasks = false
                    }
                }
            }
            .store(in: &cancellables)

        // Observe tasks by residence
        dataManager.$tasksByResidence
            .receive(on: DispatchQueue.main)
            .sink { [weak self] tasksByResidence in
                guard self?.isAnimatingCompletion != true else { return }
                // Only update if we're filtering by residence
                if let resId = self?.currentResidenceId,
                   let tasks = tasksByResidence[resId] {
                    self?.tasksResponse = tasks
                    self?.isLoadingTasks = false
                }
            }
            .store(in: &cancellables)

        // Auto-reset stale success states after 3 seconds
        $actionState
            .receive(on: DispatchQueue.main)
            .sink { [weak self] state in
                if case .success = state {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                        // Only reset if still in the same success state
                        if self?.actionState == state {
                            self?.actionState = .idle
                        }
                    }
                }
            }
            .store(in: &cancellables)
    }

    // MARK: - Public Methods
    func createTask(request: TaskCreateRequest, completion: @escaping (Bool) -> Void) {
        actionState = .loading(.create)
        errorMessage = nil

        Task {
            do {
                let result = try await APILayer.shared.createTask(request: request)

                if let error = ApiResultBridge.error(from: result) {
                    self.actionState = .error(.create, ErrorMessageParser.parse(error.message))
                    self.errorMessage = ErrorMessageParser.parse(error.message)
                    completion(false)
                } else {
                    self.actionState = .success(.create)
                    completion(true)
                }
            } catch {
                self.actionState = .error(.create, error.localizedDescription)
                self.errorMessage = ErrorMessageParser.parse(error.localizedDescription)
                completion(false)
            }
        }
    }

    func cancelTask(id: Int32, completion: @escaping (Bool) -> Void) {
        actionState = .loading(.cancel)
        errorMessage = nil

        Task {
            do {
                let result = try await APILayer.shared.cancelTask(taskId: id)

                // Check for error first, then treat non-error as success
                // This handles Kotlin-Swift generic type bridging issues
                if let error = ApiResultBridge.error(from: result) {
                    self.actionState = .error(.cancel, ErrorMessageParser.parse(error.message))
                    self.errorMessage = ErrorMessageParser.parse(error.message)
                    completion(false)
                } else {
                    // Not an error = success (DataManager is updated by APILayer)
                    self.actionState = .success(.cancel)
                    completion(true)
                }
            } catch {
                self.actionState = .error(.cancel, error.localizedDescription)
                self.errorMessage = ErrorMessageParser.parse(error.localizedDescription)
                completion(false)
            }
        }
    }

    func uncancelTask(id: Int32, completion: @escaping (Bool) -> Void) {
        actionState = .loading(.uncancel)
        errorMessage = nil

        Task {
            do {
                let result = try await APILayer.shared.uncancelTask(taskId: id)

                if let error = ApiResultBridge.error(from: result) {
                    self.actionState = .error(.uncancel, ErrorMessageParser.parse(error.message))
                    self.errorMessage = ErrorMessageParser.parse(error.message)
                    completion(false)
                } else {
                    self.actionState = .success(.uncancel)
                    completion(true)
                }
            } catch {
                self.actionState = .error(.uncancel, error.localizedDescription)
                self.errorMessage = ErrorMessageParser.parse(error.localizedDescription)
                completion(false)
            }
        }
    }

    func markInProgress(id: Int32, completion: @escaping (Bool) -> Void) {
        actionState = .loading(.markInProgress)
        errorMessage = nil

        Task {
            do {
                let result = try await APILayer.shared.markInProgress(taskId: id)

                if let error = ApiResultBridge.error(from: result) {
                    self.actionState = .error(.markInProgress, ErrorMessageParser.parse(error.message))
                    self.errorMessage = ErrorMessageParser.parse(error.message)
                    completion(false)
                } else {
                    self.actionState = .success(.markInProgress)
                    completion(true)
                }
            } catch {
                self.actionState = .error(.markInProgress, error.localizedDescription)
                self.errorMessage = ErrorMessageParser.parse(error.localizedDescription)
                completion(false)
            }
        }
    }

    func archiveTask(id: Int32, completion: @escaping (Bool) -> Void) {
        actionState = .loading(.archive)
        errorMessage = nil

        Task {
            do {
                let result = try await APILayer.shared.archiveTask(taskId: id)

                if let error = ApiResultBridge.error(from: result) {
                    self.actionState = .error(.archive, ErrorMessageParser.parse(error.message))
                    self.errorMessage = ErrorMessageParser.parse(error.message)
                    completion(false)
                } else {
                    self.actionState = .success(.archive)
                    completion(true)
                }
            } catch {
                self.actionState = .error(.archive, error.localizedDescription)
                self.errorMessage = ErrorMessageParser.parse(error.localizedDescription)
                completion(false)
            }
        }
    }

    func unarchiveTask(id: Int32, completion: @escaping (Bool) -> Void) {
        actionState = .loading(.unarchive)
        errorMessage = nil

        Task {
            do {
                let result = try await APILayer.shared.unarchiveTask(taskId: id)

                if let error = ApiResultBridge.error(from: result) {
                    self.actionState = .error(.unarchive, ErrorMessageParser.parse(error.message))
                    self.errorMessage = ErrorMessageParser.parse(error.message)
                    completion(false)
                } else {
                    self.actionState = .success(.unarchive)
                    completion(true)
                }
            } catch {
                self.actionState = .error(.unarchive, error.localizedDescription)
                self.errorMessage = ErrorMessageParser.parse(error.localizedDescription)
                completion(false)
            }
        }
    }

    func updateTask(id: Int32, request: TaskCreateRequest, completion: @escaping (Bool) -> Void) {
        actionState = .loading(.update)
        errorMessage = nil

        Task {
            do {
                let result = try await APILayer.shared.updateTask(id: id, request: request)

                if let error = ApiResultBridge.error(from: result) {
                    self.actionState = .error(.update, ErrorMessageParser.parse(error.message))
                    self.errorMessage = ErrorMessageParser.parse(error.message)
                    completion(false)
                } else {
                    self.actionState = .success(.update)
                    completion(true)
                }
            } catch {
                self.actionState = .error(.update, error.localizedDescription)
                self.errorMessage = ErrorMessageParser.parse(error.localizedDescription)
                completion(false)
            }
        }
    }

    func clearError() {
        errorMessage = nil
        if case .error = actionState {
            actionState = .idle
        }
    }

    func resetState() {
        actionState = .idle
        errorMessage = nil
    }

    /// Resets the action state to idle, clearing any stale success/error messages.
    /// Call this after consuming a success state (e.g., after dismissing a form).
    func resetActionState() {
        actionState = .idle
    }

    // MARK: - Task Completions

    func loadCompletions(taskId: Int32) {
        isLoadingCompletions = true
        completionsError = nil

        Task {
            do {
                let result = try await APILayer.shared.getTaskCompletions(taskId: taskId)

                if let success = result as? ApiResultSuccess<NSArray> {
                    self.completions = (success.data as? [TaskCompletionResponse]) ?? []
                    self.isLoadingCompletions = false
                } else if let error = ApiResultBridge.error(from: result) {
                    self.completionsError = ErrorMessageParser.parse(error.message)
                    self.isLoadingCompletions = false
                }
            } catch {
                self.completionsError = ErrorMessageParser.parse(error.localizedDescription)
                self.isLoadingCompletions = false
            }
        }
    }

    func resetCompletionsState() {
        completions = []
        completionsError = nil
        isLoadingCompletions = false
    }

    // MARK: - Kanban Board Methods

    /// Count total tasks across all columns
    var totalTaskCount: Int {
        guard let response = tasksResponse else { return 0 }
        return response.columns.reduce(0) { $0 + $1.tasks.count }
    }

    /// Check if there are no tasks
    var hasNoTasks: Bool {
        guard let response = tasksResponse else { return true }
        return response.columns.allSatisfy { $0.tasks.isEmpty }
    }

    /// Check if there are tasks
    var hasTasks: Bool {
        !hasNoTasks
    }

    /// Load tasks - either all tasks or filtered by residence
    /// Kotlin's APILayer handles caching - returns cached data if valid without network call.
    /// - Parameters:
    ///   - residenceId: Optional residence ID to filter by. If nil, loads all tasks.
    ///   - forceRefresh: Whether to bypass cache
    func loadTasks(residenceId: Int32? = nil, forceRefresh: Bool = false) {
        guard DataManager.shared.isAuthenticated() else { return }

        currentResidenceId = residenceId
        tasksError = nil
        isLoadingTasks = true

        // Kick off API call - DataManager will be updated, which updates DataManagerObservable,
        // which updates our @Published tasksResponse via the sink above
        Task {
            do {
                let result: Any
                if let resId = residenceId {
                    result = try await APILayer.shared.getTasksByResidence(
                        residenceId: resId,
                        forceRefresh: forceRefresh
                    )
                } else {
                    result = try await APILayer.shared.getTasks(forceRefresh: forceRefresh)
                }

                // Handle all result states
                await MainActor.run {
                    if let success = result as? ApiResultSuccess<TaskColumnsResponse>,
                       let data = success.data {
                        // Update widget data if loading all tasks
                        if residenceId == nil {
                            WidgetDataManager.shared.saveTasks(from: data)
                        }
                        // tasksResponse is updated via DataManagerObservable observation
                        // Ensure loading state is cleared on success
                        self.isLoadingTasks = false
                    } else if let error = ApiResultBridge.error(from: result) {
                        self.tasksError = error.message
                        self.isLoadingTasks = false
                    } else {
                        self.tasksError = "Failed to load tasks"
                        self.isLoadingTasks = false
                    }
                }
            } catch {
                await MainActor.run {
                    self.tasksError = ErrorMessageParser.parse(error.localizedDescription)
                    self.isLoadingTasks = false
                }
            }
        }
    }

    /// Updates a task in the kanban board by moving it to the correct column based on kanban_column
    func updateTaskInKanban(_ updatedTask: TaskResponse) {
        guard let currentResponse = tasksResponse else { return }

        let targetColumn = updatedTask.kanbanColumn ?? "completed_tasks"

        // Build new columns array
        var newColumns: [TaskColumn] = []

        for column in currentResponse.columns {
            // Remove task from this column if it exists
            var filteredTasks = column.tasks.filter { $0.id != updatedTask.id }

            // Add task to target column
            if column.name == targetColumn {
                filteredTasks.append(updatedTask)
            }

            // Create new column with updated tasks and count
            let newColumn = TaskColumn(
                name: column.name,
                displayName: column.displayName,
                buttonTypes: column.buttonTypes,
                icons: column.icons,
                color: column.color,
                tasks: filteredTasks,
                count: Int32(filteredTasks.count)
            )
            newColumns.append(newColumn)
        }

        // Update the response
        tasksResponse = TaskColumnsResponse(
            columns: newColumns,
            daysThreshold: currentResponse.daysThreshold,
            residenceId: currentResponse.residenceId
        )
    }

    /// Removes a task from the kanban board (after deletion)
    func removeTaskFromKanban(taskId: Int32) {
        guard let currentResponse = tasksResponse else { return }

        var newColumns: [TaskColumn] = []

        for column in currentResponse.columns {
            let filteredTasks = column.tasks.filter { $0.id != taskId }

            let newColumn = TaskColumn(
                name: column.name,
                displayName: column.displayName,
                buttonTypes: column.buttonTypes,
                icons: column.icons,
                color: column.color,
                tasks: filteredTasks,
                count: Int32(filteredTasks.count)
            )
            newColumns.append(newColumn)
        }

        tasksResponse = TaskColumnsResponse(
            columns: newColumns,
            daysThreshold: currentResponse.daysThreshold,
            residenceId: currentResponse.residenceId
        )
    }

    /// Reloads the kanban board with current settings
    func reloadTasks() {
        loadTasks(residenceId: currentResidenceId, forceRefresh: true)
    }
}
