import UIKit

enum RepositoryMode {
    case issues
    case commits
    case milestones
}

@MainActor
final class ServersViewController: UITableViewController {
    private let context: AppContext
    private var servers: [ServerRow] = []

    init(context: AppContext) {
        self.context = context
        super.init(style: .plain)
        title = "Servers"
        tableView.backgroundColor = .systemGroupedBackground
        tableView.separatorInset = .zero
    }

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

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        servers = context.core.servers()
        tableView.reloadData()
        tableView.backgroundView = servers.isEmpty
            ? EmptyBackgroundView(title: "No servers", detail: "Add a Gitea server to get started.")
            : nil
        navigationItem.rightBarButtonItem = UIBarButtonItem(
            systemItem: .add,
            primaryAction: UIAction { [weak self] _ in self?.showAddServer() }
        )
    }

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

    override func tableView(
        _ tableView: UITableView,
        cellForRowAt indexPath: IndexPath
    ) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "server")
            ?? UITableViewCell(style: .subtitle, reuseIdentifier: "server")
        let server = servers[indexPath.row]
        configureTextCell(cell, title: server.name, detail: server.url)
        cell.accessoryType = .disclosureIndicator
        return cell
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        do {
            try context.selectServer(index: UInt32(indexPath.row))
        } catch {
            show(error: error)
        }
    }

    private func showAddServer() {
        let controller = AddServerViewController(context: context) { [weak self] in
            guard let self else { return }
            self.servers = self.context.core.servers()
            self.tableView.reloadData()
        }
        present(UINavigationController(rootViewController: controller), animated: true)
    }
}

@MainActor
final class AddServerViewController: UITableViewController, UITextFieldDelegate {
    private let context: AppContext
    private let completion: () -> Void
    private let nameField = UITextField()
    private let urlField = UITextField()
    private let tokenField = UITextField()
    private var saveButton: UIBarButtonItem!

    init(context: AppContext, completion: @escaping () -> Void) {
        self.context = context
        self.completion = completion
        super.init(style: .insetGrouped)
        title = "Add Server"
    }

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

    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.leftBarButtonItem = UIBarButtonItem(
            systemItem: .cancel,
            primaryAction: UIAction { [weak self] _ in self?.dismiss(animated: true) }
        )
        saveButton = UIBarButtonItem(
            title: "Add",
            style: .done,
            target: self,
            action: #selector(save)
        )
        navigationItem.rightBarButtonItem = saveButton
        configure(nameField, placeholder: "Work", contentType: .name)
        configure(urlField, placeholder: "https://gitea.example.com", contentType: .URL)
        urlField.keyboardType = .URL
        urlField.autocapitalizationType = .none
        configure(tokenField, placeholder: "Access token", contentType: nil)
        tokenField.isSecureTextEntry = true
        tokenField.autocapitalizationType = .none
        tokenField.returnKeyType = .done
    }

    override func numberOfSections(in tableView: UITableView) -> Int { 3 }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 1 }

    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        ["Name", "Server URL", "Access token"][section]
    }

    override func tableView(
        _ tableView: UITableView,
        cellForRowAt indexPath: IndexPath
    ) -> UITableViewCell {
        let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
        let field = [nameField, urlField, tokenField][indexPath.section]
        field.translatesAutoresizingMaskIntoConstraints = false
        cell.contentView.addSubview(field)
        NSLayoutConstraint.activate([
            field.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 16),
            field.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -16),
            field.topAnchor.constraint(equalTo: cell.contentView.topAnchor),
            field.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor),
            cell.contentView.heightAnchor.constraint(greaterThanOrEqualToConstant: 48),
        ])
        return cell
    }

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        if textField === nameField { urlField.becomeFirstResponder() }
        else if textField === urlField { tokenField.becomeFirstResponder() }
        else { save() }
        return true
    }

    @objc private func save() {
        view.endEditing(true)
        saveButton.isEnabled = false
        let spinner = UIActivityIndicatorView(style: .medium)
        spinner.startAnimating()
        navigationItem.rightBarButtonItem = UIBarButtonItem(customView: spinner)
        Task {
            do {
                let index = try await context.core.addServer(
                    name: nameField.text ?? "",
                    url: urlField.text ?? "",
                    token: tokenField.text ?? ""
                )
                try context.didAddServer(index: index)
                completion()
                dismiss(animated: true)
            } catch {
                navigationItem.rightBarButtonItem = saveButton
                saveButton.isEnabled = true
                show(error: error)
            }
        }
    }

    private func configure(
        _ field: UITextField,
        placeholder: String,
        contentType: UITextContentType?
    ) {
        field.placeholder = placeholder
        field.textContentType = contentType
        field.clearButtonMode = .whileEditing
        field.delegate = self
        field.returnKeyType = .next
        field.adjustsFontForContentSizeCategory = true
        field.font = .preferredFont(forTextStyle: .body)
    }
}

@MainActor
final class RepositoriesViewController: RefreshingTableViewController {
    private let context: AppContext
    private let mode: RepositoryMode
    private var rows: [RepositoryRow] = []

    init(context: AppContext, mode: RepositoryMode) {
        self.context = context
        self.mode = mode
        super.init()
        title = context.core.activeServerName() ?? "Repositories"
    }

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

    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.leftBarButtonItem = UIBarButtonItem(
            image: context.symbol("server.rack"),
            primaryAction: UIAction { [weak self] _ in
                guard let self else { return }
                self.navigationController?.pushViewController(
                    ServersViewController(context: self.context),
                    animated: true
                )
            }
        )
        loadContent(refreshing: false)
    }

    override func loadContent(refreshing: Bool) {
        beginLoading(refreshing: refreshing)
        loadingTask?.cancel()
        loadingTask = Task {
            do {
                rows = try await context.core.repositories()
                tableView.reloadData()
                tableView.backgroundView = rows.isEmpty
                    ? EmptyBackgroundView(
                        title: "No repositories",
                        detail: "This account does not own any repositories on this server."
                    )
                    : nil
            } catch {
                if !Task.isCancelled { show(error: error) }
            }
            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: "repository")
            ?? UITableViewCell(style: .subtitle, reuseIdentifier: "repository")
        let row = rows[indexPath.row]
        configureTextCell(cell, title: row.name, detail: "\(row.description)\n\(row.meta)")
        let button = UIButton(type: .system, primaryAction: UIAction { [weak self] _ in
            self?.toggleFavorite(row)
        })
        button.setImage(context.symbol(row.favorite ? "star.fill" : "star"), for: .normal)
        button.tintColor = row.favorite ? .systemYellow : .tertiaryLabel
        button.frame.size = CGSize(width: 44, height: 44)
        cell.accessoryView = button
        return cell
    }

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        96
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        let row = rows[indexPath.row]
        let destination: UIViewController
        switch mode {
        case .issues:
            destination = IssuesViewController(
                context: context,
                owner: row.owner,
                repository: row.name
            )
        case .commits:
            destination = CommitsViewController(
                context: context,
                owner: row.owner,
                repository: row.name
            )
        case .milestones:
            destination = MilestonesViewController(
                context: context,
                owner: row.owner,
                repository: row.name
            )
        }
        navigationController?.pushViewController(destination, animated: true)
    }

    private func toggleFavorite(_ row: RepositoryRow) {
        do {
            rows = try context.core.toggleFavorite(owner: row.owner, repository: row.name)
            tableView.reloadData()
        } catch {
            show(error: error)
        }
    }
}

@MainActor
final class HomeViewController: RefreshingTableViewController {
    private let context: AppContext
    private var page: HomePage?

    init(context: AppContext) {
        self.context = context
        super.init()
        title = "Home"
    }

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

    override func viewDidLoad() {
        super.viewDidLoad()
        let settings = UIBarButtonItem(
            image: context.symbol("gearshape"),
            primaryAction: UIAction { [weak self] _ in
                guard let self else { return }
                self.navigationController?.pushViewController(
                    SettingsViewController(context: self.context),
                    animated: true
                )
            }
        )
        settings.accessibilityLabel = "Settings"
        navigationItem.rightBarButtonItem = settings
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        guard page == nil else { return }
        loadContent(refreshing: false)
    }

    override func loadContent(refreshing: Bool) {
        guard context.core.activeServerIndex() != nil else {
            tableView.backgroundView = EmptyBackgroundView(
                title: "No server selected",
                detail: "Open Issues or Repos to select a server or add your first one."
            )
            refreshControl?.endRefreshing()
            return
        }
        beginLoading(refreshing: refreshing)
        loadingTask?.cancel()
        loadingTask = Task {
            do {
                page = try await context.core.home()
                title = page?.serverName
                tableView.tableHeaderView = page.map { HeatmapView(page: $0) }
                tableView.reloadData()
                tableView.backgroundView = nil
            } catch {
                if !Task.isCancelled { show(error: error) }
            }
            endLoading()
        }
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        page?.activities.count ?? 0
    }

    override func tableView(
        _ tableView: UITableView,
        cellForRowAt indexPath: IndexPath
    ) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "activity")
            ?? UITableViewCell(style: .subtitle, reuseIdentifier: "activity")
        guard let row = page?.activities[indexPath.row] else { return cell }
        configureTextCell(
            cell,
            title: row.title,
            detail: "\(row.detail)\n\(row.meta)",
            image: context.symbol(symbolName(for: row.icon))
        )
        cell.accessoryType = row.target.isEmpty ? .none : .disclosureIndicator
        return cell
    }

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        92
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        if let row = page?.activities[indexPath.row] { context.route(row) }
    }

    private func symbolName(for icon: String) -> String {
        switch icon {
        case let value where value.contains("pull"): return "arrow.triangle.pull"
        case let value where value.contains("issue"): return "exclamationmark.circle"
        case let value where value.contains("branch"): return "arrow.triangle.branch"
        case let value where value.contains("tag"): return "tag"
        case "push": return "arrow.up.circle"
        case "release": return "shippingbox"
        default: return "books.vertical"
        }
    }
}

final class HeatmapView: UIView {
    private let cells: [HeatCell]
    private let calendar = Calendar(identifier: .gregorian)

    init(page: HomePage) {
        let latest = page.heatCells.last.map {
            Date(timeIntervalSince1970: TimeInterval($0.timestamp))
        } ?? Date()
        let latestMonth = Calendar(identifier: .gregorian).date(
            from: Calendar(identifier: .gregorian).dateComponents([.year, .month], from: latest)
        ) ?? latest
        let firstMonth = Calendar(identifier: .gregorian).date(
            byAdding: .month,
            value: -8,
            to: latestMonth
        ) ?? latestMonth
        cells = page.heatCells.filter {
            Date(timeIntervalSince1970: TimeInterval($0.timestamp)) >= firstMonth
        }
        super.init(frame: CGRect(x: 0, y: 0, width: 0, height: 132))
        backgroundColor = .systemBackground
        let title = UILabel(frame: CGRect(x: 16, y: 12, width: 300, height: 22))
        title.text = "Activity · last 9 months"
        title.font = .preferredFont(forTextStyle: .subheadline)
        title.textColor = .secondaryLabel
        addSubview(title)
        let total = UILabel(frame: CGRect(x: 16, y: 108, width: 300, height: 18))
        total.text = "\(cells.reduce(0) { $0 + $1.contributions }) contributions"
        total.font = .preferredFont(forTextStyle: .caption1)
        total.textColor = .tertiaryLabel
        addSubview(total)
    }

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

    override func draw(_ rect: CGRect) {
        guard
            let first = cells.first,
            let last = cells.last
        else { return }
        let firstDate = Date(timeIntervalSince1970: TimeInterval(first.timestamp))
        let lastDate = Date(timeIntervalSince1970: TimeInterval(last.timestamp))
        guard let firstWeek = calendar.dateInterval(of: .weekOfYear, for: firstDate)?.start else {
            return
        }
        let monthFormatter = DateFormatter()
        monthFormatter.dateFormat = "MMM"
        let months = cells.reduce(into: [Date]()) { result, cell in
            let date = Date(timeIntervalSince1970: TimeInterval(cell.timestamp))
            let month = calendar.date(
                from: calendar.dateComponents([.year, .month], from: date)
            ) ?? date
            if result.last != month { result.append(month) }
        }
        let weekCount = CGFloat(
            (calendar.dateComponents([.day], from: firstWeek, to: lastDate).day ?? 0) / 7 + 1
        )
        let monthGap: CGFloat = 4
        let width = max(4, min(6, (bounds.width - 32) / weekCount - 1))
        let gap = width + 1
        let labelAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.preferredFont(forTextStyle: .caption2),
            .foregroundColor: UIColor.secondaryLabel,
        ]
        for (index, month) in months.enumerated() {
            let days = calendar.dateComponents([.day], from: firstWeek, to: month).day ?? 0
            monthFormatter.string(from: month).draw(
                at: CGPoint(
                    x: 16 + CGFloat(days / 7) * gap + CGFloat(index) * monthGap,
                    y: 34
                ),
                withAttributes: labelAttributes
            )
        }
        for cell in cells {
            let date = Date(timeIntervalSince1970: TimeInterval(cell.timestamp))
            let days = calendar.dateComponents([.day], from: firstWeek, to: date).day ?? 0
            let month = calendar.date(
                from: calendar.dateComponents([.year, .month], from: date)
            ) ?? date
            let monthIndex = months.firstIndex(of: month) ?? 0
            let colors: [UIColor] = [
                .systemGray5,
                UIColor(red: 0.72, green: 0.85, blue: 0.96, alpha: 1),
                UIColor(red: 0.45, green: 0.71, blue: 0.91, alpha: 1),
                UIColor(red: 0.15, green: 0.55, blue: 0.83, alpha: 1),
                UIColor(red: 0.04, green: 0.41, blue: 0.72, alpha: 1),
            ]
            colors[Int(min(cell.level, 4))].setFill()
            UIBezierPath(
                roundedRect: CGRect(
                    x: 16 + CGFloat(days / 7) * gap + CGFloat(monthIndex) * monthGap,
                    y: 48 + CGFloat(calendar.component(.weekday, from: date) - 1) * gap,
                    width: width,
                    height: width
                ),
                cornerRadius: 1
            ).fill()
        }
    }
}
