import ForgejoKit
import SwiftUI

struct MergedNotificationsOverviewView: View {
    let manager: MultiInstanceManager

    @State private var pagination: PaginationState<TaggedItem<NotificationThread>>
    @State private var statusFilter: String = "unread"
    @Environment(NavigationState.self) private var navigationState

    init(manager: MultiInstanceManager) {
        self.manager = manager
        let state = PaginationState<TaggedItem<NotificationThread>>()
        state.cacheName = "merged_notifications"
        state.dedupeKey = { $0.id }
        state.loadFromCache()
        state.rehydrate(from: manager)
        _pagination = State(initialValue: state)
    }

    private var statusTypes: [String] {
        switch statusFilter {
        case "unread": ["unread"]
        case "read": ["read"]
        default: ["unread", "read"]
        }
    }

    var body: some View {
        @Bindable var pagination = pagination
        VStack(spacing: 0) {
            if !manager.failedInstances.isEmpty {
                MergedConnectionWarning(failedInstances: manager.failedInstances)
            }

            SegmentedPickerSection(
                title: "Status",
                selection: $statusFilter,
                options: [("Unread", "unread"), ("Read", "read"), ("All", "all")],
                accessibilityIdentifier: "notification-status-picker",
            )

            List {
                if pagination.isLoading, pagination.items.isEmpty {
                    LoadingListSection()
                } else if pagination.items.isEmpty {
                    ContentUnavailableView {
                        Label("No Notifications", systemImage: "bell.slash")
                            .foregroundStyle(statusFilter == "unread" ? .green : .secondary)
                    } description: {
                        Text(
                            statusFilter == "unread"
                                ? "You're all caught up!"
                                : "There are no \(statusFilter) notifications",
                        )
                    }
                } else {
                    Section {
                        ForEach(pagination.items) { tagged in
                            notificationRow(tagged)
                                .swipeActions(edge: .leading) {
                                    if tagged.item.unread {
                                        Button {
                                            Task { await markAsRead(tagged) }
                                        } label: {
                                            Label("Mark Read", systemImage: "envelope.open")
                                        }
                                        .tint(.blue)
                                    }
                                }
                        }

                        if pagination.hasMore {
                            ProgressView()
                                .frame(maxWidth: .infinity)
                                .listRowBackground(Color.clear)
                                .accessibilityIdentifier("load-more-indicator")
                                .task {
                                    await loadMore()
                                }
                        }
                    }
                }
            }
            .listStyle(.insetGrouped)
        }
        .navigationTitle("Notifications")
        .refreshable {
            await reloadNotifications().value
        }
        .task {
            if !pagination.hasLoaded {
                reloadNotifications()
            }
        }
        .onChange(of: statusFilter) {
            reloadNotifications(clearItems: true)
        }
        .onChange(of: navigationState.notificationsRefreshTrigger) {
            reloadNotifications()
        }
        .errorAlert(message: $pagination.errorMessage, isPresented: $pagination.showError)
    }

    @ViewBuilder
    private func notificationRow(_ tagged: TaggedItem<NotificationThread>) -> some View {
        let notification = tagged.item
        let destination = navigationDestination(for: tagged)
        if let destination {
            NavigationLink {
                destination
                    .onAppear { Task { await markReadOnOpen(tagged) } }
            } label: {
                MergedNotificationRow(notification: notification, instanceName: tagged.instanceName)
            }
        } else {
            MergedNotificationRow(notification: notification, instanceName: tagged.instanceName)
        }
    }

    @ViewBuilder
    private func navigationDestination(for tagged: TaggedItem<NotificationThread>) -> (some View)? {
        let notification = tagged.item
        switch notification.subject.type {
        case "Issue":
            if let number = subjectNumber(from: notification) {
                IssueDetailView(
                    repository: notification.repository,
                    issueNumber: number,
                    authService: tagged.authService,
                )
            }
        case "Pull":
            if let number = subjectNumber(from: notification) {
                PullRequestDetailView(
                    repository: notification.repository,
                    prNumber: number,
                    authService: tagged.authService,
                )
            }
        default:
            nil as EmptyView?
        }
    }

    private func subjectNumber(from notification: NotificationThread) -> Int? {
        notificationSubjectNumber(from: notification.subject.url)
            ?? notificationSubjectNumber(from: notification.subject.htmlUrl)
    }

    @discardableResult
    private func reloadNotifications(clearItems: Bool = false) -> Task<Void, Never> {
        pagination.reloadAndCache(clearItems: clearItems) { page, limit in
            try await fetchFromAllInstances(page: page, limit: limit)
        }
    }

    private func loadMore() async {
        await pagination.loadMoreAndCache { page, limit in
            try await fetchFromAllInstances(page: page, limit: limit)
        }
    }

    private func fetchFromAllInstances(page: Int, limit: Int) async throws -> [TaggedItem<NotificationThread>] {
        let types = statusTypes

        let sources = manager.connectionSources()

        let batches = try await withThrowingTaskGroup(of: (Int, [NotificationThread]).self) { group in
            for (index, source) in sources.enumerated() {
                let client = source.client
                group.addTask {
                    let service = NotificationService(client: client)
                    return try await (index, resilientFetch {
                        try await service.fetchNotifications(
                            statusTypes: types, page: page, limit: limit,
                        )
                    })
                }
            }
            return try await group.reduce(into: [(Int, [NotificationThread])]()) { $0.append($1) }
        }

        return TaggedItem.mergeAndDeduplicate(batches: batches, sources: sources)
            .sorted { $0.item.updatedAt > $1.item.updatedAt }
    }

    private func markAsRead(_ tagged: TaggedItem<NotificationThread>) async {
        await setNotificationRead(tagged)
    }

    /// Marks a notification read when the user opens it, mirroring Mail and News.
    /// The row is updated in place rather than removed so navigation into the
    /// detail view is not interrupted while the list is mutating.
    private func markReadOnOpen(_ tagged: TaggedItem<NotificationThread>) async {
        guard tagged.item.unread else { return }
        await setNotificationRead(tagged, removeFromUnread: false)
    }

    private func setNotificationRead(_ tagged: TaggedItem<NotificationThread>, removeFromUnread: Bool = true) async {
        guard let client = tagged.authService.client else { return }
        let service = NotificationService(client: client)
        let notification = tagged.item
        do {
            try await service.markAsRead(id: notification.id)
            withAnimation {
                if removeFromUnread, statusFilter == "unread" {
                    pagination.items.removeAll { $0.id == tagged.id }
                } else if let index = pagination.items.firstIndex(where: { $0.id == tagged.id }) {
                    let updated = NotificationThread(
                        id: notification.id,
                        unread: false,
                        pinned: notification.pinned,
                        updatedAt: notification.updatedAt,
                        url: notification.url,
                        subject: notification.subject,
                        repository: notification.repository,
                    )
                    pagination.items[index] = TaggedItem(
                        item: updated,
                        sourceKey: tagged.sourceKey,
                        instanceName: tagged.instanceName,
                        authService: tagged.authService,
                    )
                }
            }
            NotificationCenter.default.post(name: .notificationsDidChange, object: nil)
        } catch {
            pagination.errorMessage = error.localizedDescription
            pagination.showError = true
        }
    }
}

private struct MergedNotificationRow: View {
    let notification: NotificationThread
    var instanceName: String?

    private var typeIcon: String {
        switch notification.subject.type {
        case "Issue": "exclamationmark.circle"
        case "Pull": "arrow.triangle.pull"
        case "Commit": "circle.dotted.circle"
        case "Repository": "folder"
        default: "bell"
        }
    }

    private var stateColor: Color {
        switch notification.subject.stateValue {
        case .open: .green
        case .closed, .merged: .purple
        default: .secondary
        }
    }

    var body: some View {
        HStack(alignment: .top, spacing: 10) {
            Image(systemName: typeIcon)
                .foregroundStyle(stateColor)
                .font(.body)
                .padding(.top, 2)

            VStack(alignment: .leading, spacing: 4) {
                HStack(spacing: 6) {
                    Text(notification.subject.title)
                        .font(.headline)
                        .lineLimit(2)
                    if let instanceName {
                        InstanceBadge(name: instanceName)
                    }
                }

                HStack(spacing: 4) {
                    Text(notification.repository.fullName)
                        .lineLimit(1)
                        .truncationMode(.middle)
                    Text("·")
                        .foregroundStyle(.tertiary)
                    Text(formatRelativeDate(notification.updatedAt))
                        .fixedSize()
                }
                .font(.caption)
                .foregroundStyle(.secondary)
            }

            Spacer()

            if notification.unread {
                Circle()
                    .fill(.blue)
                    .frame(width: 10, height: 10)
                    .padding(.top, 6)
            }
        }
        .padding(.vertical, 2)
    }
}
