import ForgejoKit
import SwiftData
import SwiftUI

struct InstanceListView: View {
    @Environment(\.modelContext) private var modelContext
    @Query(sort: \ForgejoInstance.lastUsed, order: .reverse) private var instances: [ForgejoInstance]
    @AppStorage("defaultAllInstances") private var defaultAllInstances = false
    @State private var authService: AuthenticationService
    @Binding var multiInstanceManager: MultiInstanceManager?
    @State private var showAddSheet = false
    @State private var editingInstance: ForgejoInstance?
    @State private var connectingInstance: ForgejoInstance?
    @State private var connectingAll = false
    @State private var errorMessage: String?
    @State private var showError = false
    @State private var errorTitle = Self.connectionFailedTitle

    private static let connectionFailedTitle = "Connection Failed"
    private static let saveFailedTitle = "Save Failed"

    init(authService: AuthenticationService, multiInstanceManager: Binding<MultiInstanceManager?>) {
        self.authService = authService
        _multiInstanceManager = multiInstanceManager
    }

    var body: some View {
        NavigationStack {
            Group {
                if instances.isEmpty {
                    ContentUnavailableView {
                        Label("No Instances", systemImage: "server.rack")
                            .foregroundStyle(.blue)
                    } description: {
                        Text("Add your first Forgejo instance to get started.")
                    }
                } else {
                    List {
                        if instances.count > 1 {
                            allInstancesRow
                                .swipeActions(edge: .leading, allowsFullSwipe: true) {
                                    Button {
                                        toggleDefaultAll()
                                    } label: {
                                        Label(
                                            defaultAllInstances ? "Unset Default" : "Set Default",
                                            systemImage: defaultAllInstances ? "star.slash" : "star.fill",
                                        )
                                    }
                                    .tint(.yellow)
                                }
                        }
                        ForEach(instances) { instance in
                            instanceRow(instance)
                                .swipeActions(edge: .trailing, allowsFullSwipe: false) {
                                    Button(role: .destructive) {
                                        deleteInstance(instance)
                                    } label: {
                                        Label("Delete", systemImage: "trash")
                                    }
                                }
                                .swipeActions(edge: .leading, allowsFullSwipe: true) {
                                    Button {
                                        toggleDefault(instance)
                                    } label: {
                                        Label(
                                            instance.isDefault ? "Unset Default" : "Set Default",
                                            systemImage: instance.isDefault ? "star.slash" : "star.fill",
                                        )
                                    }
                                    .tint(.yellow)
                                }
                        }
                    }
                    .accessibilityIdentifier("instance-list")
                }
            }
            .navigationTitle("Instances")
            .toolbar {
                ToolbarItem(placement: .primaryAction) {
                    Button {
                        showAddSheet = true
                    } label: {
                        Image(systemName: "plus")
                    }
                    .accessibilityLabel("Add instance")
                    .accessibilityIdentifier("instance-add-button")
                }
            }
            .sheet(isPresented: $showAddSheet) {
                InstanceFormView(authService: authService, mode: .add)
            }
            .sheet(item: $editingInstance) { instance in
                InstanceFormView(authService: authService, mode: .edit(instance))
            }
            .errorAlert(errorTitle, message: $errorMessage, isPresented: $showError)
        }
    }

    private func instanceRow(_ instance: ForgejoInstance) -> some View {
        Button {
            connect(to: instance)
        } label: {
            HStack {
                VStack(alignment: .leading, spacing: 4) {
                    HStack(spacing: 8) {
                        Text(instance.name.isEmpty ? instance.serverURL : instance.name)
                            .font(.headline)
                        if instance.isDefault {
                            Text("Default")
                                .font(.caption2)
                                .fontWeight(.medium)
                                .padding(.horizontal, 6)
                                .padding(.vertical, 2)
                                .glassEffect(.regular.tint(.yellow))
                        }
                    }
                    Text(instance.serverURL)
                        .font(.subheadline)
                        .foregroundStyle(.secondary)
                    Text("@\(instance.username)")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                }
                Spacer()
                if connectingInstance?.id == instance.id {
                    ProgressView()
                } else {
                    Button {
                        editingInstance = instance
                    } label: {
                        Image(systemName: "pencil.circle.fill")
                            .font(.title3)
                            .foregroundStyle(.secondary)
                    }
                    .buttonStyle(.plain)
                    .accessibilityLabel("Edit instance")
                    .accessibilityIdentifier("instance-edit-button")
                }
            }
        }
        .disabled(connectingInstance != nil)
        .accessibilityIdentifier("instance-row")
    }

    private var allInstancesRow: some View {
        Button {
            connectAll()
        } label: {
            HStack {
                VStack(alignment: .leading, spacing: 4) {
                    HStack(spacing: 8) {
                        Text("All Instances")
                            .font(.headline)
                        if defaultAllInstances {
                            Text("Default")
                                .font(.caption2)
                                .fontWeight(.medium)
                                .padding(.horizontal, 6)
                                .padding(.vertical, 2)
                                .glassEffect(.regular.tint(.yellow))
                        }
                    }
                    Text("\(instances.count) instances")
                        .font(.subheadline)
                        .foregroundStyle(.secondary)
                }
                Spacer()
                if connectingAll {
                    ProgressView()
                } else {
                    Image(systemName: "rectangle.stack.fill")
                        .foregroundStyle(.blue)
                        .font(.title3)
                }
            }
        }
        .disabled(connectingInstance != nil || connectingAll)
        .accessibilityIdentifier("all-instances-row")
    }

    private func connectAll() {
        connectingAll = true
        let manager = MultiInstanceManager()
        Task {
            await manager.connect(instances: instances)
            if manager.isConnected {
                multiInstanceManager = manager
            } else {
                errorMessage = "Could not connect to any instance"
                showError = true
            }
            connectingAll = false
        }
    }

    private func connect(to instance: ForgejoInstance) {
        connectingInstance = instance
        Task {
            do {
                try await authService.restoreSession(instance: instance)
                instance.lastUsed = Date()
                persist()
            } catch is KeychainError {
                editingInstance = instance
            } catch {
                errorTitle = Self.connectionFailedTitle
                errorMessage = error.localizedDescription
                showError = true
            }
            connectingInstance = nil
        }
    }

    private func deleteInstance(_ instance: ForgejoInstance) {
        let wasActive = authService.currentInstance?.id == instance.id
        let normalizedURL = ForgejoClient.normalizeServerURL(instance.serverURL)
        let username = instance.username

        // Remove the instance from SwiftData first. A failed save rolls the delete
        // back, so the credentials have to stay put or the restored account would be
        // left unauthenticatable.
        modelContext.delete(instance)
        guard persist() else { return }

        if wasActive {
            authService.disconnect()
        }
        Task {
            try? await KeychainManager.shared.deleteCredentials(for: normalizedURL, username: username)
        }
    }

    private func toggleDefaultAll() {
        defaultAllInstances.toggle()
        if defaultAllInstances {
            ForgejoInstance.clearDefaults(in: instances)
            if !persist() {
                // The stored per-instance defaults survived the rollback, so the
                // @AppStorage flag must go back to match them.
                defaultAllInstances = false
            }
        }
    }

    private func toggleDefault(_ instance: ForgejoInstance) {
        let previousDefaultAll = defaultAllInstances
        if instance.isDefault {
            instance.isDefault = false
        } else {
            ForgejoInstance.clearDefaults(in: instances)
            instance.isDefault = true
            defaultAllInstances = false
        }
        if !persist() {
            defaultAllInstances = previousDefaultAll
        }
    }

    /// Saves pending changes, surfacing a failure to the user. Returns false when the
    /// save failed and the context was rolled back.
    @discardableResult
    private func persist() -> Bool {
        guard let failure = modelContext.saveOrRollback() else { return true }
        errorTitle = Self.saveFailedTitle
        errorMessage = failure
        showError = true
        return false
    }
}

#if DEBUG
    #Preview {
        InstanceListView(authService: .previewDefault, multiInstanceManager: .constant(nil))
            .modelContainer(for: ForgejoInstance.self, inMemory: true)
    }
#endif
