import ForgejoKit
import SwiftUI

struct PullRequestEditView: View {
    let repository: Repository
    let pullRequest: PullRequest
    @State private var authService: AuthenticationService
    @State private var title: String
    @State private var bodyText: String
    @State private var isOpen: Bool
    @State private var isSubmitting = false
    @State private var errorMessage: String?
    @State private var showError = false
    @State private var availableLabels: [IssueLabel] = []
    @State private var availableMilestones: [IssueMilestone] = []
    @State private var availableAssignees: [User] = []
    @State private var selectedLabelIDs: Set<Int>
    @State private var selectedMilestoneID: Int?
    @State private var selectedAssigneeLogins: Set<String>
    @State private var selectedReviewerLogins: Set<String>
    @Environment(\.dismiss) private var dismiss

    private let prService: PullRequestService?
    private let repositoryService: RepositoryService?
    private let onUploadImage: ((Data, String, String) async throws -> String)?
    private let onSaved: (PullRequest) -> Void

    private var isMerged: Bool {
        pullRequest.merged == true
    }

    init(
        repository: Repository, pullRequest: PullRequest,
        authService: AuthenticationService,
        onUploadImage: ((Data, String, String) async throws -> String)? = nil,
        onSaved: @escaping (PullRequest) -> Void,
    ) {
        self.repository = repository
        self.pullRequest = pullRequest
        self.authService = authService
        prService = authService.client.map { PullRequestService(client: $0) }
        repositoryService = authService.client.map { RepositoryService(client: $0) }
        self.onUploadImage = onUploadImage
        self.onSaved = onSaved
        _title = State(initialValue: pullRequest.title)
        _bodyText = State(initialValue: pullRequest.body ?? "")
        _isOpen = State(initialValue: pullRequest.stateValue == .open)
        _selectedLabelIDs = State(initialValue: Set(pullRequest.labels.map(\.id)))
        _selectedMilestoneID = State(initialValue: pullRequest.milestone?.id)
        _selectedAssigneeLogins = State(initialValue: Set((pullRequest.assignees ?? []).map(\.login)))
        _selectedReviewerLogins = State(initialValue: Set((pullRequest.requestedReviewers ?? []).map(\.login)))
    }

    private var owner: String {
        repository.owner
    }

    private var repo: String {
        repository.repoName
    }

    var body: some View {
        NavigationStack {
            Form {
                Section {
                    TextField("Title", text: $title)
                        .accessibilityIdentifier("pr-edit-title-field")
                }

                DescriptionEditorSection(text: $bodyText, onUploadImage: onUploadImage)

                LabelPickerSection(
                    availableLabels: availableLabels,
                    selectedLabelIDs: $selectedLabelIDs,
                )
                MilestonePickerSection(
                    availableMilestones: availableMilestones,
                    selectedMilestoneID: $selectedMilestoneID,
                )
                UserPickerSection(
                    title: "Assignees",
                    availableUsers: availableAssignees,
                    selectedLogins: $selectedAssigneeLogins,
                )
                UserPickerSection(
                    title: "Reviewers",
                    availableUsers: availableAssignees,
                    selectedLogins: $selectedReviewerLogins,
                )

                if !isMerged {
                    Section {
                        Toggle("Open", isOn: $isOpen)
                    }
                }
            }
            .navigationTitle("Edit Pull Request")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Cancel") { dismiss() }
                }
                ToolbarItem(placement: .confirmationAction) {
                    Button("Save") {
                        Task { await savePullRequest() }
                    }
                    .disabled(title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || isSubmitting)
                    .accessibilityIdentifier("pr-edit-save")
                }
            }
            .errorAlert(message: $errorMessage, isPresented: $showError)
            .task {
                await loadMetadata()
            }
        }
    }

    private func loadMetadata() async {
        guard let repositoryService else { return }
        let metadata = await loadRepositoryMetadata(service: repositoryService, owner: owner, repo: repo)
        availableLabels = metadata.labels
        availableMilestones = metadata.milestones
        availableAssignees = metadata.assignees
    }

    // swiftlint:disable:next function_body_length
    private func savePullRequest() async {
        guard let prService, let client = authService.client else { return }
        isSubmitting = true
        do {
            let issueService = IssueService(client: client)
            try await issueService.replaceLabels(
                owner: owner,
                repo: repo,
                index: pullRequest.number,
                labelIDs: Array(selectedLabelIDs),
            )
            _ = try await issueService.editIssue(
                owner: owner,
                repo: repo,
                index: pullRequest.number,
                title: nil,
                body: nil,
                state: nil,
                milestone: selectedMilestoneID ?? 0, // 0 clears the milestone per Forgejo API
            )
            let updatedPR = try await prService.editPullRequest(
                owner: owner,
                repo: repo,
                index: pullRequest.number,
                title: title.trimmingCharacters(in: .whitespacesAndNewlines),
                body: bodyText.trimmingCharacters(in: .whitespacesAndNewlines),
                state: isMerged ? nil : (isOpen ? PullRequestState.open.rawValue : PullRequestState.closed.rawValue),
                assignees: Array(selectedAssigneeLogins),
            )
            let initialReviewerLogins = Set((pullRequest.requestedReviewers ?? []).map(\.login))
            let addedReviewers = selectedReviewerLogins.subtracting(initialReviewerLogins)
            let removedReviewers = initialReviewerLogins.subtracting(selectedReviewerLogins)
            if !addedReviewers.isEmpty {
                try await prService.requestReviewers(
                    owner: owner,
                    repo: repo,
                    index: pullRequest.number,
                    reviewers: Array(addedReviewers),
                )
            }
            if !removedReviewers.isEmpty {
                try await prService.removeReviewers(
                    owner: owner,
                    repo: repo,
                    index: pullRequest.number,
                    reviewers: Array(removedReviewers),
                )
            }
            NotificationCenter.default.post(name: .pullRequestsDidChange, object: nil)
            onSaved(updatedPR)
            dismiss()
        } catch {
            // The calls above run in sequence with no transaction, so an earlier
            // one may already be committed on the server. Re-fetch the PR and push
            // the authoritative state to the detail view so it does not show stale
            // data, then surface the error and keep the sheet open to retry.
            if let refreshed = try? await prService.fetchPullRequest(
                owner: owner, repo: repo, index: pullRequest.number,
            ) {
                NotificationCenter.default.post(name: .pullRequestsDidChange, object: nil)
                onSaved(refreshed)
            }
            errorMessage = error.localizedDescription
            showError = true
        }
        isSubmitting = false
    }

    #if DEBUG
        init(preview _: Void, repository: Repository, pullRequest: PullRequest, authService: AuthenticationService,
             availableLabels: [IssueLabel] = [], availableMilestones: [IssueMilestone] = [],
             availableAssignees: [User] = [])
        {
            self.repository = repository
            self.pullRequest = pullRequest
            self.authService = authService
            prService = nil
            repositoryService = nil
            onUploadImage = nil
            onSaved = { _ in }
            _title = State(initialValue: pullRequest.title)
            _bodyText = State(initialValue: pullRequest.body ?? "")
            _isOpen = State(initialValue: pullRequest.stateValue == .open)
            _selectedLabelIDs = State(initialValue: Set(pullRequest.labels.map(\.id)))
            _selectedMilestoneID = State(initialValue: pullRequest.milestone?.id)
            _selectedAssigneeLogins = State(initialValue: Set((pullRequest.assignees ?? []).map(\.login)))
            _selectedReviewerLogins = State(initialValue: Set((pullRequest.requestedReviewers ?? []).map(\.login)))
            _availableLabels = State(initialValue: availableLabels)
            _availableMilestones = State(initialValue: availableMilestones)
            _availableAssignees = State(initialValue: availableAssignees)
        }
    #endif
}

#if DEBUG
    #Preview {
        PullRequestEditView(
            preview: (),
            repository: .preview,
            pullRequest: .preview,
            authService: .previewDefault,
            availableLabels: IssueLabel.previewList,
            availableMilestones: [.preview],
            availableAssignees: [.preview, .previewBot],
        )
    }
#endif
