import UIKit

@MainActor
final class MilestonesViewController: RefreshingTableViewController {
    private let context: AppContext
    private let owner: String
    private let repository: String
    private var rows: [MilestoneRow] = []
    private var mutationTask: Task<Void, Never>?
    private var currentPage: UInt32 = 0

    init(context: AppContext, owner: String, repository: String) {
        self.context = context
        self.owner = owner
        self.repository = repository
        super.init()
        title = repository
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }

    deinit { mutationTask?.cancel() }

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(MilestoneCell.self, forCellReuseIdentifier: "milestone")
        tableView.rowHeight = UITableView.automaticDimension
        tableView.estimatedRowHeight = 118
        let addButton = UIBarButtonItem(
            barButtonSystemItem: .add,
            target: self,
            action: #selector(createMilestone)
        )
        addButton.accessibilityLabel = "New milestone"
        navigationItem.rightBarButtonItem = addButton
        loadContent(refreshing: false)
    }

    @objc private func createMilestone() {
        let editor = MilestoneEditorViewController(
            context: context,
            owner: owner,
            repository: repository
        ) { [weak self] _ in
            guard let self else { return }
            self.dismiss(animated: true) { self.loadContent(refreshing: false) }
        }
        present(UINavigationController(rootViewController: editor), animated: true)
    }

    override func loadContent(refreshing: Bool) {
        loadPage(1, refreshing: refreshing)
    }

    override func loadMoreContent() {
        loadPage(currentPage + 1, refreshing: false)
    }

    private func loadPage(_ page: UInt32, refreshing: Bool) {
        if page == 1 {
            resetPagination()
            beginLoading(refreshing: refreshing)
        }
        loadingTask?.cancel()
        loadingTask = Task {
            do {
                let result = try await context.core.milestones(
                    owner: owner,
                    repository: repository,
                    page: page
                )
                if page == 1 { rows = result.rows } else { rows.append(contentsOf: result.rows) }
                currentPage = page
                finishPagination(hasMore: result.hasMore)
                tableView.reloadData()
                tableView.backgroundView = rows.isEmpty
                    ? EmptyBackgroundView(
                        title: "No milestones",
                        detail: "This repository does not have any milestones."
                    )
                    : nil
            } catch {
                if !Task.isCancelled {
                    show(error: error)
                    failPagination()
                }
            }
            if page == 1 { endLoading() }
        }
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        rows.count
    }

    override func tableView(
        _ tableView: UITableView,
        cellForRowAt indexPath: IndexPath
    ) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(
            withIdentifier: "milestone",
            for: indexPath
        ) as! MilestoneCell
        cell.configure(rows[indexPath.row], disclosure: true)
        return cell
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        navigationController?.pushViewController(
            MilestoneViewController(
                context: context,
                owner: owner,
                repository: repository,
                id: rows[indexPath.row].id
            ),
            animated: true
        )
    }

    override func tableView(
        _ tableView: UITableView,
        trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath
    ) -> UISwipeActionsConfiguration? {
        let row = rows[indexPath.row]
        let close = row.state != .closed
        let stateAction = UIContextualAction(
            style: .normal,
            title: close ? "Close" : "Open"
        ) { [weak self] _, _, completion in
            self?.setMilestone(row, closed: close, completion: completion) ?? completion(false)
        }
        stateAction.image = context.symbol(close ? "checkmark.circle" : "arrow.uturn.left.circle")
        stateAction.backgroundColor = close ? .systemPurple : .systemGreen

        let deleteAction = UIContextualAction(style: .destructive, title: "Delete") {
            [weak self] _, _, completion in
            self?.confirmDelete(row, completion: completion) ?? completion(false)
        }
        deleteAction.image = context.symbol("trash")

        let configuration = UISwipeActionsConfiguration(actions: [stateAction, deleteAction])
        configuration.performsFirstActionWithFullSwipe = true
        return configuration
    }

    private func setMilestone(
        _ row: MilestoneRow,
        closed: Bool,
        completion: @escaping (Bool) -> Void
    ) {
        mutationTask?.cancel()
        mutationTask = Task {
            do {
                try await context.core.setMilestoneClosed(
                    owner: owner,
                    repository: repository,
                    id: row.id,
                    closed: closed
                )
                guard !Task.isCancelled else {
                    completion(false)
                    return
                }
                completion(true)
                loadContent(refreshing: false)
            } catch {
                completion(false)
                if !Task.isCancelled { show(error: error) }
            }
        }
    }

    private func confirmDelete(_ row: MilestoneRow, completion: @escaping (Bool) -> Void) {
        guard !row.hasIssues else {
            let alert = UIAlertController(
                title: "Milestone Can’t Be Deleted",
                message: "Remove all assigned issues and pull requests first.",
                preferredStyle: .alert
            )
            alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in completion(false) })
            present(alert, animated: true)
            return
        }
        let alert = UIAlertController(
            title: "Delete \(row.title)?",
            message: "This milestone will be permanently deleted. This can’t be undone.",
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in completion(false) })
        alert.addAction(UIAlertAction(title: "Delete", style: .destructive) { [weak self] _ in
            guard let self else {
                completion(false)
                return
            }
            self.mutationTask?.cancel()
            self.mutationTask = Task {
                do {
                    try await self.context.core.deleteMilestone(
                        owner: self.owner,
                        repository: self.repository,
                        id: row.id
                    )
                    guard !Task.isCancelled else {
                        completion(false)
                        return
                    }
                    completion(true)
                    self.loadContent(refreshing: false)
                } catch {
                    completion(false)
                    if !Task.isCancelled { self.show(error: error) }
                }
            }
        })
        present(alert, animated: true)
    }
}

@MainActor
final class MilestoneViewController: RefreshingTableViewController, IssueSwipeActionHost {
    let context: AppContext
    let owner: String
    let repository: String
    private let id: Int64
    private var page: MilestonePage?
    var issueMutationTask: Task<Void, Never>?
    private var currentPage: UInt32 = 0

    init(context: AppContext, owner: String, repository: String, id: Int64) {
        self.context = context
        self.owner = owner
        self.repository = repository
        self.id = id
        super.init()
        title = "Milestone"
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }

    deinit { issueMutationTask?.cancel() }

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(MilestoneCell.self, forCellReuseIdentifier: "milestone")
        tableView.register(IssueCell.self, forCellReuseIdentifier: "issue")
        tableView.register(PullCell.self, forCellReuseIdentifier: "pull")
        tableView.rowHeight = UITableView.automaticDimension
        tableView.estimatedRowHeight = 118
        let editButton = UIBarButtonItem(
            image: context.symbol("pencil"),
            style: .plain,
            target: self,
            action: #selector(editMilestone)
        )
        editButton.accessibilityLabel = "Edit milestone"
        navigationItem.rightBarButtonItem = editButton
        loadContent(refreshing: false)
    }

    @objc private func editMilestone() {
        let editor = MilestoneEditorViewController(
            context: context,
            owner: owner,
            repository: repository,
            id: id
        ) { [weak self] _ in
            guard let self else { return }
            self.dismiss(animated: true) { self.loadContent(refreshing: false) }
        }
        present(UINavigationController(rootViewController: editor), animated: true)
    }

    override func loadContent(refreshing: Bool) {
        loadPage(1, refreshing: refreshing)
    }

    override func loadMoreContent() {
        loadPage(currentPage + 1, refreshing: false)
    }

    private func loadPage(_ requestedPage: UInt32, refreshing: Bool) {
        if requestedPage == 1 {
            resetPagination()
            beginLoading(refreshing: refreshing)
        }
        loadingTask?.cancel()
        loadingTask = Task {
            do {
                let result = try await context.core.milestone(
                    owner: owner,
                    repository: repository,
                    id: id,
                    page: requestedPage
                )
                if requestedPage == 1 {
                    page = result
                } else {
                    page?.issues.append(contentsOf: result.issues)
                    page?.pulls.append(contentsOf: result.pulls)
                    page?.hasMore = result.hasMore
                }
                currentPage = requestedPage
                finishPagination(hasMore: result.hasMore)
                title = page?.milestone.title
                tableView.reloadData()
            } catch {
                if !Task.isCancelled {
                    show(error: error)
                    failPagination()
                }
            }
            if requestedPage == 1 { endLoading() }
        }
    }

    override func numberOfSections(in tableView: UITableView) -> Int { page == nil ? 0 : 3 }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        switch section {
        case 0: 1
        case 1: page?.issues.count ?? 0
        default: page?.pulls.count ?? 0
        }
    }

    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        switch section {
        case 1: "Issues"
        case 2: "Pull Requests"
        default: nil
        }
    }

    override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
        if section == 1, page?.issues.isEmpty == true {
            return "No issues are assigned to this milestone."
        }
        if section == 2, page?.pulls.isEmpty == true {
            return "No pull requests are assigned to this milestone."
        }
        return nil
    }

    override func tableView(
        _ tableView: UITableView,
        cellForRowAt indexPath: IndexPath
    ) -> UITableViewCell {
        guard let page else { return UITableViewCell() }
        if indexPath.section == 0 {
            let cell = tableView.dequeueReusableCell(
                withIdentifier: "milestone",
                for: indexPath
            ) as! MilestoneCell
            cell.configure(page.milestone, disclosure: false)
            return cell
        }
        if indexPath.section == 1 {
            let cell = tableView.dequeueReusableCell(withIdentifier: "issue", for: indexPath) as! IssueCell
            cell.configure(page.issues[indexPath.row])
            return cell
        }
        let cell = tableView.dequeueReusableCell(withIdentifier: "pull", for: indexPath) as! PullCell
        cell.configure(page.pulls[indexPath.row])
        return cell
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        if indexPath.section == 1, let issue = page?.issues[indexPath.row] {
            navigationController?.pushViewController(
                IssueViewController(
                    context: context,
                    owner: owner,
                    repository: repository,
                    number: issue.number
                ),
                animated: true
            )
        } else if indexPath.section == 2, let pull = page?.pulls[indexPath.row] {
            navigationController?.pushViewController(
                PullViewController(
                    context: context,
                    owner: pull.owner,
                    repository: pull.repository,
                    number: pull.number
                ),
                animated: true
            )
        }
    }

    override func tableView(
        _ tableView: UITableView,
        trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath
    ) -> UISwipeActionsConfiguration? {
        guard indexPath.section == 1, let issue = page?.issues[indexPath.row] else { return nil }
        return issueSwipeActions(for: issue)
    }

    func reloadIssuesAfterMutation() {
        loadContent(refreshing: false)
    }
}

final class MilestoneCell: UITableViewCell {
    private let stateIcon = UIImageView()
    private let titleLabel = UILabel()
    private let stack = UIStackView()
    private var descriptionView: UIView?
    private let metaLabel = UILabel()
    private let progress = UIProgressView(progressViewStyle: .bar)

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        titleLabel.font = .preferredFont(forTextStyle: .headline)
        titleLabel.numberOfLines = 2
        let titleStack = UIStackView(arrangedSubviews: [stateIcon, titleLabel])
        titleStack.alignment = .firstBaseline
        titleStack.spacing = 8
        metaLabel.font = .preferredFont(forTextStyle: .caption1)
        metaLabel.textColor = .tertiaryLabel
        metaLabel.numberOfLines = 2
        progress.progressTintColor = .systemGreen
        stack.addArrangedSubview(titleStack)
        stack.addArrangedSubview(progress)
        stack.addArrangedSubview(metaLabel)
        stack.axis = .vertical
        stack.spacing = 7
        stack.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(stack)
        NSLayoutConstraint.activate([
            stack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
            stack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
            stack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12),
            stack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -12),
        ])
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }

    func configure(_ row: MilestoneRow, disclosure: Bool) {
        accessoryType = disclosure ? .disclosureIndicator : .none
        configureOpenClosedStateIcon(
            stateIcon,
            state: row.state,
            subject: "milestone",
            textStyle: .headline
        )
        titleLabel.text = row.title
        descriptionView?.removeFromSuperview()
        if !row.description.isEmpty {
            let view = markdownView(row.description)
            stack.insertArrangedSubview(view, at: 1)
            descriptionView = view
        }
        metaLabel.text = row.meta
        progress.progress = Float(row.progress)
        progress.trackTintColor = row.hasIssues ? .systemOrange : .systemGray5
        progress.accessibilityLabel = "Milestone progress"
        progress.accessibilityValue = row.progressAccessibility
    }
}
