import ForgejoKit
import SwiftUI
import UIKit

/// Renders a single job's steps with collapsible logs. Backed by Forgejo's
/// experimental web-UI endpoint via `WorkflowService.fetchRunView`.
struct WorkflowJobView: View {
    let repository: Repository
    let runIndex: Int
    let jobIndex: Int
    let jobName: String
    @State private var authService: AuthenticationService

    @State private var view: WorkflowRunView?
    @State private var stepLogs: [Int: [WorkflowRunViewLogLine]] = [:]
    @State private var isLoading = false
    @State private var errorMessage: String?
    @State private var showError = false
    @State private var hasLoaded = false
    @State private var expandedSteps: Set<Int> = []

    private let workflowService: WorkflowService?

    init(
        repository: Repository,
        runIndex: Int, jobIndex: Int,
        jobName: String,
        authService: AuthenticationService,
    ) {
        self.repository = repository
        self.runIndex = runIndex
        self.jobIndex = jobIndex
        self.jobName = jobName
        self.authService = authService
        workflowService = authService.client.map { WorkflowService(client: $0) }
    }

    var body: some View {
        List {
            if let view {
                stepsSection(steps: view.state.currentJob.steps)
            } else if isLoading {
                LoadingListSection()
            } else {
                ContentUnavailableView {
                    Label("No Job Data", systemImage: "tray")
                        .foregroundStyle(.secondary)
                } description: {
                    Text("Forgejo returned no job details.")
                }
            }
        }
        .listStyle(.insetGrouped)
        .navigationTitle(jobName)
        .navigationBarTitleDisplayMode(.inline)
        .refreshable {
            await loadJob()
        }
        .task {
            if !hasLoaded {
                await loadJob()
            }
        }
        .errorAlert(message: $errorMessage, isPresented: $showError)
    }

    @ViewBuilder
    private func stepsSection(steps: [WorkflowRunViewStep]) -> some View {
        if steps.isEmpty {
            Section {
                ContentUnavailableView {
                    Label("No Steps Recorded", systemImage: "tray")
                        .foregroundStyle(.secondary)
                } description: {
                    Text("This job has no recorded steps, it likely never started.")
                }
            } header: {
                experimentalHeader
            }
        } else {
            Section {
                ForEach(Array(steps.enumerated()), id: \.offset) { index, step in
                    StepDisclosureRow(
                        step: step,
                        isExpanded: Binding(
                            get: { expandedSteps.contains(index) },
                            set: { newValue in
                                if newValue {
                                    expandedSteps.insert(index)
                                    Task { await loadLogs(for: index) }
                                } else {
                                    expandedSteps.remove(index)
                                }
                            },
                        ),
                        logLines: stepLogs[index] ?? [],
                    )
                    .accessibilityIdentifier("workflow-step-\(index)")
                }
            } header: {
                experimentalHeader
            } footer: {
                Text(
                    "Steps and logs come from Forgejo's web UI endpoint, not the public API. "
                        + "Output may change between Forgejo versions.",
                )
                .font(.caption2)
                .foregroundStyle(.tertiary)
            }
        }
    }

    private var experimentalHeader: some View {
        HStack {
            Text("Steps")
            Spacer()
            Text("Experimental")
                .font(.caption2.weight(.medium))
                .foregroundStyle(.orange)
        }
    }

    private func loadJob() async {
        guard let workflowService else { return }
        isLoading = true
        errorMessage = nil

        do {
            let fetched = try await workflowService.fetchRunView(
                owner: repository.owner,
                repo: repository.repoName,
                runIndex: runIndex,
                jobIndex: jobIndex,
                logCursors: [],
            )
            view = fetched
            mergeLogs(from: fetched)
            hasLoaded = true
        } catch is CancellationError {
            // Ignore cancellation
        } catch {
            errorMessage = error.localizedDescription
            showError = true
        }

        isLoading = false
    }

    private func loadLogs(for step: Int) async {
        guard let workflowService else { return }
        // Already loaded?
        if stepLogs[step] != nil { return }

        do {
            let fetched = try await workflowService.fetchRunView(
                owner: repository.owner,
                repo: repository.repoName,
                runIndex: runIndex,
                jobIndex: jobIndex,
                logCursors: [WorkflowLogCursor(step: step, cursor: 0, expanded: true)],
            )
            // Each fetch returns logs only for the requested cursor(s); merge
            // into our running map so previously-expanded steps don't go blank.
            mergeLogs(from: fetched)
        } catch is CancellationError {
            // Ignore
        } catch {
            errorMessage = error.localizedDescription
            showError = true
        }
    }

    private func mergeLogs(from runView: WorkflowRunView) {
        for stepLog in runView.logs.stepsLog {
            stepLogs[stepLog.step] = stepLog.lines
        }
    }
}

private struct StepDisclosureRow: View {
    let step: WorkflowRunViewStep
    @Binding var isExpanded: Bool
    let logLines: [WorkflowRunViewLogLine]

    var body: some View {
        DisclosureGroup(isExpanded: $isExpanded) {
            if logLines.isEmpty {
                Text("No log output")
                    .font(.caption)
                    .foregroundStyle(.secondary)
                    .padding(.vertical, 4)
            } else {
                VStack(alignment: .leading, spacing: 2) {
                    ForEach(logLines, id: \.index) { line in
                        Text(line.message)
                            .font(.system(.caption, design: .monospaced))
                            .textSelection(.enabled)
                            .frame(maxWidth: .infinity, alignment: .leading)
                    }
                }
                .padding(.vertical, 4)
                .contextMenu {
                    Button {
                        UIPasteboard.general.string = logLines
                            .map(\.message)
                            .joined(separator: "\n")
                    } label: {
                        Label("Copy logs", systemImage: "doc.on.doc")
                    }
                }
            }
        } label: {
            HStack(spacing: 10) {
                WorkflowStatusIcon(status: step.status)
                VStack(alignment: .leading, spacing: 2) {
                    Text(step.summary)
                        .font(.subheadline)
                    if !step.duration.isEmpty {
                        Text(step.duration)
                            .font(.caption)
                            .foregroundStyle(.secondary)
                    }
                }
            }
        }
    }
}
