import ForgejoKit

// swiftlint:disable file_length
import SwiftUI

// swiftlint:disable:next type_body_length
struct PullRequestDetailView: View {
    @State private var repository: Repository
    let prNumber: Int
    @State private var authService: AuthenticationService
    @State private var pullRequest: PullRequest?
    @State private var comments: [IssueComment] = []
    @State private var isLoading = true
    @State private var errorMessage: String?
    @State private var showError = false
    @State private var showEditSheet = false
    @State private var showMergeSheet = false
    @State private var isTogglingState = false
    @State private var showCommentSheet = false
    @State private var diffText: String?
    @State private var isDiffExpanded = false
    @State private var isLoadingDiff = false

    // Reviews
    @State private var reviews: [PullRequestReview] = []
    @State private var reviewComments: [Int: [ReviewComment]] = [:]
    @State private var parsedDiff: ParsedDiff?
    @State private var assignees: [User] = []
    @State private var showSubmitReviewSheet = false
    @State private var inlineCommentContext: InlineCommentContext?
    @State private var showActionsExpanded = false

    private let prService: PullRequestService?
    private let issueService: IssueService?
    private let repositoryService: RepositoryService?

    init(repository: Repository, prNumber: Int, authService: AuthenticationService) {
        self.repository = repository
        self.prNumber = prNumber
        self.authService = authService
        prService = authService.client.map { PullRequestService(client: $0) }
        issueService = authService.client.map { IssueService(client: $0) }
        repositoryService = authService.client.map { RepositoryService(client: $0) }
    }

    private var owner: String {
        repository.owner
    }

    private var repo: String {
        repository.repoName
    }

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

    private var hasPushPermission: Bool {
        repository.permissions?.push == true || repository.permissions?.admin == true
    }

    private var isOwnPR: Bool {
        guard let currentUser = authService.currentUser?.login,
              let prAuthor = pullRequest?.user.login
        else {
            return false
        }
        return currentUser == prAuthor
    }

    private var canEditOrClose: Bool {
        hasPushPermission || isOwnPR
    }

    private var canMerge: Bool {
        hasPushPermission
    }

    private var status: PRStatusStyle {
        PRStatusStyle(
            state: pullRequest?.stateValue ?? .closed,
            merged: pullRequest?.merged,
            draft: pullRequest?.draft,
        )
    }

    var body: some View {
        ZStack(alignment: .bottomTrailing) {
            Group {
                if isLoading, pullRequest == nil {
                    ProgressView()
                } else if let activePR = pullRequest {
                    List {
                        headerSection(activePR)

                        if let milestone = activePR.milestone {
                            MilestoneDisplaySection(milestone: milestone)
                        }

                        if let prAssignees = activePR.assignees, !prAssignees.isEmpty {
                            AssigneesDisplaySection(assignees: prAssignees)
                        }

                        requestedReviewersSection(activePR)

                        if let body = activePR.body, !body.isEmpty {
                            Section("Description") {
                                MarkdownPreview(text: body)
                                    .padding(.vertical, 4)
                            }
                        }

                        if let assets = activePR.assets, !assets.isEmpty {
                            Section("Attachments") {
                                AttachmentGallery(attachments: assets)
                                    .padding(.vertical, 4)
                            }
                        }

                        reviewsSection
                        changesSection
                        conflictSection(activePR)
                        commentsSection

                        Section {} footer: {
                            Spacer().frame(height: 60)
                        }
                    }
                    .listStyle(.insetGrouped)
                }
            }

            if let activePR = pullRequest {
                actionMenu(activePR)
            }
        }
        .navigationTitle("#\(prNumber)")
        .navigationBarTitleDisplayMode(.inline)
        .sheet(isPresented: $showEditSheet) {
            if let editingPR = pullRequest {
                PullRequestEditView(
                    repository: repository,
                    pullRequest: editingPR,
                    authService: authService,
                    onUploadImage: uploadPRImage,
                ) { _ in
                    Task { await loadData() }
                }
            }
        }
        .sheet(isPresented: $showMergeSheet) {
            if let mergingPR = pullRequest {
                PullRequestMergeView(
                    repository: repository,
                    pullRequest: mergingPR,
                    authService: authService,
                ) {
                    Task { await loadData() }
                }
            }
        }
        .sheet(isPresented: $showSubmitReviewSheet) {
            PullRequestReviewSheet(
                repository: repository,
                prNumber: prNumber,
                authService: authService,
                isOwnPR: isOwnPR,
            ) {
                Task { await loadData() }
            }
        }
        .sheet(item: $inlineCommentContext) { context in
            if let prService {
                InlineCommentSheet(
                    context: context,
                    prService: prService,
                    owner: owner,
                    repo: repo,
                    prNumber: prNumber,
                    isOwnPR: isOwnPR,
                ) {
                    Task { await loadReviews() }
                }
            }
        }
        .sheet(isPresented: $showCommentSheet) {
            CommentSheet(users: assignees, onUploadImage: uploadPRImage) { body in
                guard let prService else { throw URLError(.userAuthenticationRequired) }
                let comment = try await prService.createComment(
                    owner: owner,
                    repo: repo,
                    index: prNumber,
                    body: body,
                )
                comments.append(comment)
            }
        }
        .task {
            await loadData()
        }
        .errorAlert(message: $errorMessage, isPresented: $showError)
    }

    // MARK: - View Sections

    // swiftlint:disable:next function_body_length
    private func headerSection(_ pullReq: PullRequest) -> some View {
        Section {
            VStack(alignment: .leading, spacing: 10) {
                Text(pullReq.title)
                    .font(.title3)
                    .fontWeight(.bold)
                    .accessibilityIdentifier("pr-detail-title")

                HStack(spacing: 8) {
                    HStack(spacing: 4) {
                        Image(systemName: status.icon)
                        Text(status.text)
                    }
                    .font(.caption)
                    .fontWeight(.medium)
                    .padding(.horizontal, 8)
                    .padding(.vertical, 4)
                    .foregroundStyle(.white)
                    .glassEffect(.regular.tint(status.color))

                    Text("#\(pullReq.number)")
                        .font(.subheadline)
                        .foregroundStyle(.secondary)
                }

                Text("\(pullReq.user.login) opened \(formatRelativeDate(pullReq.createdAt))")
                    .font(.subheadline)
                    .foregroundStyle(.secondary)

                HStack(spacing: 4) {
                    Label(pullReq.head.ref, systemImage: "arrow.branch")
                        .font(.caption)
                        .padding(.horizontal, 6)
                        .padding(.vertical, 2)
                        .glassEffect(.regular)

                    Image(systemName: "arrow.right")
                        .font(.caption2)
                        .foregroundStyle(.secondary)

                    Text(pullReq.base.ref)
                        .font(.caption)
                        .padding(.horizontal, 6)
                        .padding(.vertical, 2)
                        .glassEffect(.regular)

                    Spacer()

                    if !isMerged {
                        mergeableIndicator(pullReq)
                    }
                }

                if !pullReq.labels.isEmpty {
                    FlowLayout(spacing: 4) {
                        ForEach(pullReq.labels) { label in
                            IssueLabelView(label: label)
                        }
                    }
                }
            }
            .padding(.vertical, 4)
        }
        .listRowBackground(status.color.opacity(0.08))
    }

    @ViewBuilder
    private func mergeableIndicator(_ pullReq: PullRequest) -> some View {
        if pullReq.mergeable == true {
            Image(systemName: "checkmark.circle.fill")
                .foregroundStyle(.green)
                .font(.caption)
        } else if pullReq.mergeable == false {
            Image(systemName: "xmark.circle.fill")
                .foregroundStyle(.red)
                .font(.caption)
        } else {
            Image(systemName: "questionmark.circle")
                .foregroundStyle(.secondary)
                .font(.caption)
        }
    }

    @ViewBuilder
    private func requestedReviewersSection(_ pullReq: PullRequest) -> some View {
        if let reviewers = pullReq.requestedReviewers, !reviewers.isEmpty {
            Section("Requested Reviewers") {
                ForEach(reviewers) { user in
                    HStack(spacing: 8) {
                        Image(systemName: "eye")
                            .foregroundStyle(.orange)
                        Text(user.fullName ?? user.login)
                            .font(.subheadline)
                        if user.fullName != nil {
                            Text("@\(user.login)")
                                .font(.caption)
                                .foregroundStyle(.secondary)
                        }
                    }
                }
            }
        }
    }

    @ViewBuilder
    private var reviewsSection: some View {
        if !reviews.isEmpty {
            Section("Reviews (\(reviews.count))") {
                ForEach(reviews) { review in
                    ReviewSummaryView(
                        review: review,
                        comments: reviewComments[review.id] ?? [],
                    )
                }
            }
        }
    }

    private var changesSection: some View {
        Section {
            DisclosureGroup("Changes", isExpanded: $isDiffExpanded) {
                if isLoadingDiff {
                    HStack {
                        Spacer()
                        ProgressView()
                        Spacer()
                    }
                } else if let parsed = parsedDiff {
                    DiffView(
                        diff: parsed,
                        reviewComments: allReviewComments,
                    ) { line, path in
                        inlineCommentContext = InlineCommentContext(line: line, path: path)
                    }
                    .listRowInsets(EdgeInsets())
                }
            }
            .onChange(of: isDiffExpanded) {
                if isDiffExpanded, diffText == nil, !isLoadingDiff {
                    Task { await loadDiff() }
                }
            }
        }
    }

    @ViewBuilder
    private func conflictSection(_ pullReq: PullRequest) -> some View {
        if pullReq.stateValue == .open, pullReq.mergeable == false, !isMerged {
            Section {
                HStack(spacing: 8) {
                    Image(systemName: "exclamationmark.triangle.fill")
                        .foregroundStyle(.orange)
                    Text("This pull request has conflicts that must be resolved via git or the web interface.")
                        .font(.subheadline)
                        .foregroundStyle(.secondary)
                }
            }
        }
    }

    @ViewBuilder
    private var commentsSection: some View {
        if !comments.isEmpty {
            Section("Comments (\(comments.count))") {
                ForEach(comments) { comment in
                    CommentView(
                        comment: comment,
                        currentUsername: authService.currentUser?.login,
                    ) { newBody in
                        await editComment(commentId: comment.id, body: newBody)
                    }
                }
            }
        }
    }

    // swiftlint:disable:next function_body_length
    private func actionMenu(_ pullReq: PullRequest) -> some View {
        ExpandableActionMenu(isExpanded: $showActionsExpanded) {
            Button { showCommentSheet = true } label: {
                Label("Comment", systemImage: "text.bubble")
            }
            .buttonStyle(.glassProminent)
            .transition(.move(edge: .bottom).combined(with: .opacity))
            .accessibilityIdentifier("pr-comment-button")

            if canEditOrClose {
                Button { showEditSheet = true } label: {
                    Label("Edit", systemImage: "pencil")
                }
                .buttonStyle(.glassProminent)
                .transition(.move(edge: .bottom).combined(with: .opacity))
                .accessibilityIdentifier("pr-edit-button")
            }

            if pullReq.stateValue == .open {
                Button { showSubmitReviewSheet = true } label: {
                    Label("Review", systemImage: "checkmark.message.fill")
                }
                .buttonStyle(.glassProminent)
                .tint(.blue)
                .transition(.move(edge: .bottom).combined(with: .opacity))
                .accessibilityIdentifier("pr-submit-review")
            }

            if canMerge, pullReq.stateValue == .open, pullReq.draft != true, pullReq.mergeable != false {
                Button { showMergeSheet = true } label: {
                    Label("Merge", systemImage: "arrow.triangle.merge")
                }
                .buttonStyle(.glassProminent)
                .tint(.purple)
                .transition(.move(edge: .bottom).combined(with: .opacity))
                .accessibilityIdentifier("pr-merge-button")
            }

            if !isMerged, canEditOrClose {
                Button { Task { await togglePRState() } } label: {
                    if isTogglingState {
                        ProgressView()
                            .controlSize(.small)
                    } else {
                        Label(
                            pullReq.stateValue == .open ? "Close" : "Reopen",
                            systemImage: pullReq.stateValue == .open ? "xmark.circle" : "arrow.uturn.left.circle",
                        )
                    }
                }
                .buttonStyle(.glassProminent)
                .tint(pullReq.stateValue == .open ? .red : .green)
                .disabled(isTogglingState)
                .transition(.move(edge: .bottom).combined(with: .opacity))
                .accessibilityIdentifier("pr-toggle-state")
            }
        }
    }

    // MARK: - Data Loading

    private var allReviewComments: [ReviewComment] {
        reviewComments.values.flatMap(\.self)
    }

    private func uploadPRImage(data: Data, fileName: String, mimeType: String) async throws -> String {
        guard let issueService else { throw URLError(.userAuthenticationRequired) }
        let attachment = try await issueService.uploadIssueAttachment(
            owner: owner, repo: repo, index: prNumber,
            fileData: data, fileName: fileName, mimeType: mimeType,
        )
        return attachmentMarkdown(for: attachment)
    }

    private func loadData() async {
        guard let prService, let repositoryService else { return }
        isLoading = true
        do {
            async let fetchedPR = prService.fetchPullRequest(owner: owner, repo: repo, index: prNumber)
            async let fetchedComments = prService.fetchComments(owner: owner, repo: repo, index: prNumber)
            async let fetchedReviews = prService.fetchReviews(owner: owner, repo: repo, index: prNumber)
            async let fetchedAssignees = repositoryService.fetchAssignees(owner: owner, repo: repo)
            async let fetchedRepo = repositoryService.fetchRepository(owner: owner, repo: repo)

            let loadedPR = try await fetchedPR
            let loadedComments = try await fetchedComments
            let loadedReviews = try await fetchedReviews
            let loadedAssignees = await (try? fetchedAssignees) ?? []

            try Task.checkCancellation()
            pullRequest = loadedPR
            comments = loadedComments
            reviews = loadedReviews
            assignees = loadedAssignees
            if let fullRepo = try? await fetchedRepo {
                repository = fullRepo
            }

            reviewComments = await fetchAllReviewComments(for: reviews)

            if !allReviewComments.isEmpty {
                await loadDiff()
                isDiffExpanded = true
            }
        } catch is CancellationError {
            // Ignore cancellation
        } catch {
            errorMessage = error.localizedDescription
            showError = true
        }
        isLoading = false
    }

    private func loadReviews() async {
        guard let prService else { return }
        do {
            reviews = try await prService.fetchReviews(owner: owner, repo: repo, index: prNumber)
            reviewComments = await fetchAllReviewComments(for: reviews)

            if !allReviewComments.isEmpty {
                if diffText == nil {
                    await loadDiff()
                }
                isDiffExpanded = true
            }
        } catch {
            errorMessage = error.localizedDescription
            showError = true
        }
    }

    private func fetchAllReviewComments(for reviews: [PullRequestReview]) async -> [Int: [ReviewComment]] {
        guard let prService else { return [:] }
        return await withTaskGroup(of: (Int, [ReviewComment])?.self) { group in
            for review in reviews {
                group.addTask {
                    guard let comments = try? await prService.fetchReviewComments(
                        owner: owner, repo: repo, index: prNumber, reviewId: review.id,
                    ), !comments.isEmpty else {
                        return nil
                    }
                    return (review.id, comments)
                }
            }
            var commentsMap: [Int: [ReviewComment]] = [:]
            for await result in group {
                if let (reviewId, comments) = result {
                    commentsMap[reviewId] = comments
                }
            }
            return commentsMap
        }
    }

    private func loadDiff() async {
        guard let prService else { return }
        isLoadingDiff = true
        do {
            let raw = try await prService.fetchDiff(owner: owner, repo: repo, index: prNumber)
            try Task.checkCancellation()
            diffText = raw
            parsedDiff = DiffParser.parse(raw)
        } catch is CancellationError {
            // Ignore cancellation
        } catch {
            errorMessage = error.localizedDescription
            showError = true
        }
        isLoadingDiff = false
    }

    private func togglePRState() async {
        guard let prService, let currentPR = pullRequest else { return }
        isTogglingState = true

        do {
            let newState = currentPR.stateValue == .open
                ? PullRequestState.closed.rawValue : PullRequestState.open.rawValue
            let updated = try await prService.editPullRequest(
                owner: owner,
                repo: repo,
                index: prNumber,
                title: nil,
                body: nil,
                state: newState,
            )
            pullRequest = updated
            NotificationCenter.default.post(name: .pullRequestsDidChange, object: nil)
        } catch {
            errorMessage = error.localizedDescription
            showError = true
        }

        isTogglingState = false
    }

    private func editComment(commentId: Int, body: String) async -> Bool {
        guard let prService else { return false }
        do {
            let updated = try await prService.editComment(
                owner: owner,
                repo: repo,
                commentId: commentId,
                body: body,
            )
            if let index = comments.firstIndex(where: { $0.id == commentId }) {
                comments[index] = updated
            }
            return true
        } catch {
            errorMessage = error.localizedDescription
            showError = true
            return false
        }
    }

    #if DEBUG
        init(preview _: Void, repository: Repository, prNumber: Int, authService: AuthenticationService,
             pullRequest: PullRequest, comments: [IssueComment] = [], reviews: [PullRequestReview] = [])
        {
            self.repository = repository
            self.prNumber = prNumber
            self.authService = authService
            prService = nil
            issueService = nil
            repositoryService = nil
            _pullRequest = State(initialValue: pullRequest)
            _comments = State(initialValue: comments)
            _reviews = State(initialValue: reviews)
            _isLoading = State(initialValue: false)
        }
    #endif
}

#if DEBUG
    #Preview {
        NavigationStack {
            PullRequestDetailView(
                preview: (),
                repository: .preview,
                prNumber: 4,
                authService: .previewDefault,
                pullRequest: .preview,
                comments: [.preview],
                reviews: [.preview],
            )
        }
    }
#endif
