//
//  MediaPreviewViewController.swift
//  suixinkan
//

import AVFoundation
import Kingfisher
import SnapKit
import UIKit

/// 通用全屏媒体预览页，支持图片、视频混排横向预览。
class MediaPreviewViewController: UIViewController {
    private enum Section {
        case main
    }

    private let items: [MediaPreviewItem]
    private let startIndex: Int
    private var currentIndex: Int
    private var dataSource: UICollectionViewDiffableDataSource<Section, MediaPreviewItem>!

    private lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: makeCollectionLayout())
    private let closeButton = UIButton(type: .system)
    private let counterLabel = UILabel()

    /// 创建通用媒体预览页。
    init(items: [MediaPreviewItem], startIndex: Int = 0) {
        self.items = items
        self.startIndex = MediaPreviewItem.clampedStartIndex(startIndex, itemCount: items.count)
        self.currentIndex = self.startIndex
        super.init(nibName: nil, bundle: nil)
        modalPresentationStyle = .fullScreen
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    /// 从指定控制器全屏弹出媒体预览页。
    static func present(from viewController: UIViewController, items: [MediaPreviewItem], startIndex: Int = 0) {
        guard !items.isEmpty else { return }
        let preview = MediaPreviewViewController(items: items, startIndex: startIndex)
        viewController.present(preview, animated: true)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        setupDataSource()
        applySnapshot()
        registerNotifications()
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        guard collectionView.bounds.width > 0 else { return }
        collectionView.collectionViewLayout.invalidateLayout()
        collectionView.setContentOffset(
            CGPoint(x: CGFloat(currentIndex) * collectionView.bounds.width, y: 0),
            animated: false
        )
        updateVisibleCellsPlayback()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        updateVisibleCellsPlayback()
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        pauseVisibleVideos()
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    private func setupUI() {
        view.backgroundColor = .black
        collectionView.backgroundColor = .black
        collectionView.isPagingEnabled = true
        collectionView.showsHorizontalScrollIndicator = false
        collectionView.alwaysBounceHorizontal = items.count > 1
        collectionView.delegate = self
        collectionView.register(MediaPreviewImageCell.self, forCellWithReuseIdentifier: MediaPreviewImageCell.reuseIdentifier)
        collectionView.register(MediaPreviewVideoCell.self, forCellWithReuseIdentifier: MediaPreviewVideoCell.reuseIdentifier)

        closeButton.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal)
        closeButton.tintColor = .white
        closeButton.backgroundColor = UIColor.black.withAlphaComponent(0.25)
        closeButton.layer.cornerRadius = 18
        closeButton.addTarget(self, action: #selector(closeTapped), for: .touchUpInside)

        counterLabel.textColor = .white
        counterLabel.font = .systemFont(ofSize: 14, weight: .medium)
        counterLabel.textAlignment = .center
        counterLabel.backgroundColor = UIColor.black.withAlphaComponent(0.35)
        counterLabel.layer.cornerRadius = 14
        counterLabel.clipsToBounds = true
        updateCounter()

        view.addSubview(collectionView)
        view.addSubview(closeButton)
        view.addSubview(counterLabel)

        collectionView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        closeButton.snp.makeConstraints { make in
            make.top.equalTo(view.safeAreaLayoutGuide).offset(12)
            make.trailing.equalToSuperview().inset(16)
            make.size.equalTo(36)
        }
        counterLabel.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.centerY.equalTo(closeButton)
            make.height.equalTo(28)
            make.width.greaterThanOrEqualTo(64)
        }
    }

    private func setupDataSource() {
        dataSource = UICollectionViewDiffableDataSource<Section, MediaPreviewItem>(
            collectionView: collectionView
        ) { [weak self] collectionView, indexPath, item in
            switch item.source {
            case .remoteImage, .localImageFile, .image:
                guard let cell = collectionView.dequeueReusableCell(
                    withReuseIdentifier: MediaPreviewImageCell.reuseIdentifier,
                    for: indexPath
                ) as? MediaPreviewImageCell else {
                    return UICollectionViewCell()
                }
                cell.configure(source: item.source)
                return cell
            case .remoteVideo, .localVideoFile:
                guard let cell = collectionView.dequeueReusableCell(
                    withReuseIdentifier: MediaPreviewVideoCell.reuseIdentifier,
                    for: indexPath
                ) as? MediaPreviewVideoCell else {
                    return UICollectionViewCell()
                }
                cell.configure(source: item.source)
                cell.setActive(indexPath.item == self?.currentIndex)
                return cell
            }
        }
    }

    private func applySnapshot() {
        var snapshot = NSDiffableDataSourceSnapshot<Section, MediaPreviewItem>()
        snapshot.appendSections([.main])
        snapshot.appendItems(items)
        dataSource.apply(snapshot, animatingDifferences: false)
    }

    private func makeCollectionLayout() -> UICollectionViewLayout {
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        layout.minimumLineSpacing = 0
        layout.minimumInteritemSpacing = 0
        return layout
    }

    private func registerNotifications() {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(appDidEnterBackground),
            name: UIApplication.didEnterBackgroundNotification,
            object: nil
        )
    }

    private func updateCurrentIndex() {
        guard collectionView.bounds.width > 0 else { return }
        let page = Int(round(collectionView.contentOffset.x / collectionView.bounds.width))
        currentIndex = min(max(0, page), max(0, items.count - 1))
        updateCounter()
        updateVisibleCellsPlayback()
    }

    private func updateCounter() {
        counterLabel.text = items.isEmpty ? "0/0" : "\(currentIndex + 1)/\(items.count)"
    }

    private func updateVisibleCellsPlayback() {
        for cell in collectionView.visibleCells {
            guard let indexPath = collectionView.indexPath(for: cell),
                  let videoCell = cell as? MediaPreviewVideoCell
            else { continue }
            videoCell.setActive(indexPath.item == currentIndex && view.window != nil)
        }
    }

    private func pauseVisibleVideos() {
        collectionView.visibleCells
            .compactMap { $0 as? MediaPreviewVideoCell }
            .forEach { $0.setActive(false) }
    }

    @objc private func closeTapped() {
        pauseVisibleVideos()
        dismiss(animated: true)
    }

    @objc private func appDidEnterBackground() {
        pauseVisibleVideos()
    }
}

extension MediaPreviewViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(
        _ collectionView: UICollectionView,
        layout collectionViewLayout: UICollectionViewLayout,
        sizeForItemAt indexPath: IndexPath
    ) -> CGSize {
        collectionView.bounds.size
    }

    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        updateCurrentIndex()
    }

    func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
        updateCurrentIndex()
    }

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        guard scrollView.isDragging || scrollView.isDecelerating else { return }
        pauseVisibleVideos()
    }
}

/// 图片预览 Cell，支持远程、本地与内存图片缩放。
private final class MediaPreviewImageCell: UICollectionViewCell, UIScrollViewDelegate {
    static let reuseIdentifier = "MediaPreviewImageCell"

    private let scrollView = UIScrollView()
    private let imageView = UIImageView()
    private let messageLabel = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        imageView.kf.cancelDownloadTask()
        imageView.image = nil
        messageLabel.isHidden = true
        scrollView.zoomScale = 1
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        imageView.frame = scrollView.bounds
    }

    /// 配置图片资源来源。
    func configure(source: MediaPreviewItem.Source) {
        messageLabel.isHidden = true
        scrollView.zoomScale = 1
        switch source {
        case .remoteImage(let url):
            imageView.kf.setImage(with: url) { [weak self] result in
                if case .failure = result {
                    self?.showMessage("图片加载失败")
                }
            }
        case .localImageFile(let url):
            if let image = UIImage(contentsOfFile: url.path) {
                imageView.image = image
            } else {
                showMessage("图片加载失败")
            }
        case .image(let image):
            imageView.image = image
        case .remoteVideo, .localVideoFile:
            showMessage("不支持的图片资源")
        }
    }

    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        imageView
    }

    private func setupUI() {
        backgroundColor = .black
        scrollView.delegate = self
        scrollView.minimumZoomScale = 1
        scrollView.maximumZoomScale = 3
        scrollView.showsHorizontalScrollIndicator = false
        scrollView.showsVerticalScrollIndicator = false
        scrollView.contentInsetAdjustmentBehavior = .never

        imageView.contentMode = .scaleAspectFit
        imageView.backgroundColor = .black

        messageLabel.textColor = UIColor.white.withAlphaComponent(0.8)
        messageLabel.font = .systemFont(ofSize: 15)
        messageLabel.textAlignment = .center
        messageLabel.isHidden = true

        contentView.addSubview(scrollView)
        scrollView.addSubview(imageView)
        contentView.addSubview(messageLabel)

        scrollView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        messageLabel.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.leading.trailing.equalToSuperview().inset(32)
        }

        let doubleTap = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap(_:)))
        doubleTap.numberOfTapsRequired = 2
        scrollView.addGestureRecognizer(doubleTap)
    }

    private func showMessage(_ text: String) {
        imageView.image = nil
        messageLabel.text = text
        messageLabel.isHidden = false
    }

    @objc private func handleDoubleTap(_ gesture: UITapGestureRecognizer) {
        if scrollView.zoomScale > 1 {
            scrollView.setZoomScale(1, animated: true)
            return
        }
        let point = gesture.location(in: imageView)
        let targetScale = scrollView.maximumZoomScale
        let width = scrollView.bounds.width / targetScale
        let height = scrollView.bounds.height / targetScale
        let rect = CGRect(x: point.x - width / 2, y: point.y - height / 2, width: width, height: height)
        scrollView.zoom(to: rect, animated: true)
    }
}

/// 视频预览 Cell，负责单个 AVPlayer 的播放与暂停。
private final class MediaPreviewVideoCell: UICollectionViewCell, UIGestureRecognizerDelegate {
    static let reuseIdentifier = "MediaPreviewVideoCell"

    private let playerView = MediaPreviewPlayerView()
    private let playButton = MediaPreviewPlaybackButton()
    private let messageLabel = UILabel()
    private var player: AVPlayer?
    private var didReachEnd = false
    private var isActive = false
    private var isUserPaused = false

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        NotificationCenter.default.removeObserver(self)
        player?.pause()
        player = nil
        playerView.playerLayer.player = nil
        didReachEnd = false
        isActive = false
        isUserPaused = false
        messageLabel.isHidden = true
        playButton.isHidden = false
    }

    /// 配置视频资源来源。
    func configure(source: MediaPreviewItem.Source) {
        messageLabel.isHidden = true
        didReachEnd = false
        isUserPaused = false
        let url: URL?
        switch source {
        case .remoteVideo(let remoteURL), .localVideoFile(let remoteURL):
            url = remoteURL
        case .remoteImage, .localImageFile, .image:
            url = nil
        }
        guard let url else {
            showMessage("视频加载失败")
            return
        }

        let player = AVPlayer(url: url)
        player.actionAtItemEnd = .pause
        self.player = player
        playerView.playerLayer.player = player
        if let item = player.currentItem {
            NotificationCenter.default.addObserver(
                self,
                selector: #selector(videoDidEnd),
                name: .AVPlayerItemDidPlayToEndTime,
                object: item
            )
        }
    }

    /// 根据是否为当前页切换视频播放状态。
    func setActive(_ active: Bool) {
        isActive = active
        guard let player else { return }
        guard active else {
            player.pause()
            playButton.isHidden = !didReachEnd
            return
        }
        if didReachEnd {
            showPlaybackButton(systemName: "pause.fill")
        } else if isUserPaused {
            showPlaybackButton(systemName: "play.fill")
            player.pause()
        } else {
            playButton.isHidden = true
            player.play()
        }
    }

    private func setupUI() {
        backgroundColor = .black
        playerView.playerLayer.videoGravity = .resizeAspect

        playButton.setSymbol(named: "pause.fill")
        playButton.addTarget(self, action: #selector(playTapped), for: .touchUpInside)

        messageLabel.textColor = UIColor.white.withAlphaComponent(0.8)
        messageLabel.font = .systemFont(ofSize: 15)
        messageLabel.textAlignment = .center
        messageLabel.isHidden = true

        contentView.addSubview(playerView)
        contentView.addSubview(playButton)
        contentView.addSubview(messageLabel)

        playerView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        playButton.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.size.equalTo(96)
        }
        messageLabel.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.leading.trailing.equalToSuperview().inset(32)
        }

        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(videoTapped))
        tapGesture.delegate = self
        contentView.addGestureRecognizer(tapGesture)
    }

    private func showMessage(_ text: String) {
        messageLabel.text = text
        messageLabel.isHidden = false
        playButton.isHidden = true
    }

    private func showPlaybackButton(systemName: String) {
        playButton.setSymbol(named: systemName)
        playButton.isHidden = false
    }

    private func startPlaybackFromCurrentPosition() {
        guard let player else { return }
        if didReachEnd {
            player.seek(to: .zero)
            didReachEnd = false
        }
        isUserPaused = false
        playButton.isHidden = true
        player.play()
    }

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        guard touch.view?.isDescendant(of: playButton) != true else { return false }
        return true
    }

    @objc private func playTapped() {
        startPlaybackFromCurrentPosition()
    }

    @objc private func videoTapped() {
        guard isActive, let player else { return }
        if didReachEnd || isUserPaused {
            startPlaybackFromCurrentPosition()
            return
        }
        isUserPaused = true
        player.pause()
        showPlaybackButton(systemName: "play.fill")
    }

    @objc private func videoDidEnd() {
        didReachEnd = true
        isUserPaused = false
        showPlaybackButton(systemName: "pause.fill")
    }
}

/// 承载 AVPlayerLayer 的视频预览视图。
private final class MediaPreviewPlayerView: UIView {
    override class var layerClass: AnyClass { AVPlayerLayer.self }
    var playerLayer: AVPlayerLayer { layer as! AVPlayerLayer }
}

/// 视频预览播放状态按钮，提供大号图标与柔和渐变背景。
private final class MediaPreviewPlaybackButton: UIButton {
    private let gradientLayer = CAGradientLayer()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        gradientLayer.frame = bounds
        gradientLayer.cornerRadius = bounds.width / 2
        layer.cornerRadius = bounds.width / 2
    }

    /// 设置按钮当前展示的 SF Symbol。
    func setSymbol(named systemName: String) {
        let configuration = UIImage.SymbolConfiguration(pointSize: 42, weight: .semibold)
        setImage(UIImage(systemName: systemName, withConfiguration: configuration), for: .normal)
    }

    private func setupUI() {
        tintColor = .white
        backgroundColor = UIColor.white.withAlphaComponent(0.08)
        var buttonConfiguration = UIButton.Configuration.plain()
        buttonConfiguration.baseForegroundColor = .white
        buttonConfiguration.contentInsets = .zero
        configuration = buttonConfiguration
        configurationUpdateHandler = { button in
            var updatedConfiguration = button.configuration
            updatedConfiguration?.baseForegroundColor = .white
            button.configuration = updatedConfiguration
            button.alpha = 1
            button.imageView?.alpha = 1
        }
        layer.masksToBounds = false
        layer.shadowColor = UIColor.black.cgColor
        layer.shadowOpacity = 0.28
        layer.shadowRadius = 18
        layer.shadowOffset = CGSize(width: 0, height: 10)

        gradientLayer.colors = [
            UIColor.white.withAlphaComponent(0.32).cgColor,
            UIColor.black.withAlphaComponent(0.38).cgColor
        ]
        gradientLayer.startPoint = CGPoint(x: 0.18, y: 0.12)
        gradientLayer.endPoint = CGPoint(x: 0.88, y: 0.92)
        layer.insertSublayer(gradientLayer, at: 0)

        setSymbol(named: "pause.fill")
    }
}
