import AVFoundation
import UIKit

final class VideoPlayerViewController: UIViewController {
    private let videoURL: URL
    private let player: AVPlayer
    private let playerLayer = AVPlayerLayer()
    private let controlsView = MobilePlaybackControlsView()
    private let progressInfoView = MobileProgressBarInfoView()
    private let closeButton = UIButton(type: .system)
    private var timeObserverToken: Any?
    private var endObserver: NSObjectProtocol?
    private var isScrubbing = false
    private var wasPlayingBeforeScrub = false
    private var controlsAutoHideWorkItem: DispatchWorkItem?
    private var areControlsVisible = true

    init(url: URL) {
        self.videoURL = url
        self.player = AVPlayer(url: url)
        super.init(nibName: nil, bundle: nil)
        modalPresentationStyle = .fullScreen
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        configureView()
        configurePlayer()
        configureHierarchy()
        configureActions()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        player.play()
        controlsView.controllerState = .pause
        showControls(animated: false)
        scheduleControlsAutoHide()
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        playerLayer.frame = view.bounds
    }

    deinit {
        if let timeObserverToken {
            player.removeTimeObserver(timeObserverToken)
        }
        if let endObserver {
            NotificationCenter.default.removeObserver(endObserver)
        }
        controlsAutoHideWorkItem?.cancel()
    }

    private func configureView() {
        view.backgroundColor = .black

        playerLayer.videoGravity = .resizeAspect
        view.layer.addSublayer(playerLayer)

        controlsView.controllerState = .play
        controlsView.sideControlsMode = PlayerControlsConfiguration.sideControlsMode
        controlsView.translatesAutoresizingMaskIntoConstraints = false

        progressInfoView.translatesAutoresizingMaskIntoConstraints = false
        progressInfoView.configuration = MobileProgressBarInfoConfiguration(
            title: titleText,
            info: videoURL.host ?? videoURL.scheme ?? "Video",
            currentTime: "00:00",
            totalTime: "--:--",
            progress: 0,
            showsInfo: false
        )

        closeButton.setImage(UIImage(systemName: "xmark"), for: .normal)
        closeButton.tintColor = .white
        closeButton.backgroundColor = UIColor.black.withAlphaComponent(0.35)
        closeButton.layer.cornerRadius = 20
        closeButton.translatesAutoresizingMaskIntoConstraints = false
        closeButton.accessibilityLabel = "Close player"

        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handlePlayerTap(_:)))
        tapGesture.cancelsTouchesInView = false
        tapGesture.delegate = self
        view.addGestureRecognizer(tapGesture)
    }

    private func configurePlayer() {
        playerLayer.player = player

        let interval = CMTime(seconds: 0.25, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
        timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] _ in
            self?.updateProgressInfo()
        }

        endObserver = NotificationCenter.default.addObserver(
            forName: .AVPlayerItemDidPlayToEndTime,
            object: player.currentItem,
            queue: .main
        ) { [weak self] _ in
            self?.controlsView.controllerState = .replay
            self?.updateProgressInfo()
            self?.showControls(animated: true)
        }
    }

    private func configureHierarchy() {
        view.addSubview(progressInfoView)
        view.addSubview(controlsView)
        view.addSubview(closeButton)

        NSLayoutConstraint.activate([
            closeButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16),
            closeButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
            closeButton.widthAnchor.constraint(equalToConstant: 40),
            closeButton.heightAnchor.constraint(equalToConstant: 40),

            controlsView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            controlsView.centerYAnchor.constraint(equalTo: view.centerYAnchor),

            progressInfoView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            progressInfoView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            progressInfoView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            progressInfoView.heightAnchor.constraint(equalToConstant: 106)
        ])
    }

    private func configureActions() {
        closeButton.addTarget(self, action: #selector(closePlayer), for: .touchUpInside)
        controlsView.mainControllerButton.addTarget(self, action: #selector(togglePlayback), for: .touchUpInside)
        configureSideControlActions()

        progressInfoView.onScrubbingBegan = { [weak self] in
            guard let self else { return }
            self.controlsAutoHideWorkItem?.cancel()
            self.showControls(animated: true)
            self.isScrubbing = true
            self.wasPlayingBeforeScrub = self.player.timeControlStatus == .playing
            self.player.pause()
        }

        progressInfoView.onScrubbingChanged = { [weak self] progress in
            self?.previewScrubbedProgress(progress)
        }

        progressInfoView.onScrubbingEnded = { [weak self] progress in
            self?.finishScrubbing(progress)
        }
    }

    private func configureSideControlActions() {
        switch controlsView.sideControlsMode {
        case .skip15:
            controlsView.leftSideButton.addTarget(self, action: #selector(seekBackward15), for: .touchUpInside)
            controlsView.rightSideButton.addTarget(self, action: #selector(seekForward15), for: .touchUpInside)
        case .playlist:
            controlsView.leftSideButton.addTarget(self, action: #selector(playPreviousVideo), for: .touchUpInside)
            controlsView.rightSideButton.addTarget(self, action: #selector(playNextVideo), for: .touchUpInside)
        case .hidden:
            break
        }
    }

    @objc private func closePlayer() {
        controlsAutoHideWorkItem?.cancel()
        player.pause()
        dismiss(animated: true)
    }

    @objc private func togglePlayback() {
        registerControlsInteraction()

        if controlsView.controllerState == .replay {
            seek(to: 0) { [weak self] in
                self?.player.play()
                self?.controlsView.controllerState = .pause
                self?.scheduleControlsAutoHide()
            }
            return
        }

        if player.timeControlStatus == .playing {
            player.pause()
            controlsView.controllerState = .play
        } else {
            player.play()
            controlsView.controllerState = .pause
        }
    }

    @objc private func seekBackward15() {
        registerControlsInteraction()
        seek(to: max(currentSeconds - 15, 0))
    }

    @objc private func seekForward15() {
        registerControlsInteraction()
        let duration = durationSeconds
        let target = duration > 0 ? min(currentSeconds + 15, duration) : currentSeconds + 15
        seek(to: target)
    }

    @objc private func playPreviousVideo() {
        registerControlsInteraction()
        UIImpactFeedbackGenerator(style: .light).impactOccurred()
    }

    @objc private func playNextVideo() {
        registerControlsInteraction()
        UIImpactFeedbackGenerator(style: .light).impactOccurred()
    }

    @objc private func handlePlayerTap(_ gesture: UITapGestureRecognizer) {
        guard gesture.state == .ended else {
            return
        }

        if areControlsVisible {
            hideControls(animated: true)
        } else {
            showControls(animated: true)
            scheduleControlsAutoHide()
        }
    }

    private func registerControlsInteraction() {
        showControls(animated: true)
        scheduleControlsAutoHide()
    }

    private func scheduleControlsAutoHide() {
        controlsAutoHideWorkItem?.cancel()

        guard areControlsVisible, !isScrubbing else {
            return
        }

        let workItem = DispatchWorkItem { [weak self] in
            self?.hideControls(animated: true)
        }
        controlsAutoHideWorkItem = workItem
        DispatchQueue.main.asyncAfter(deadline: .now() + 4, execute: workItem)
    }

    private func showControls(animated: Bool) {
        setControlsVisible(true, animated: animated)
    }

    private func hideControls(animated: Bool) {
        controlsAutoHideWorkItem?.cancel()
        setControlsVisible(false, animated: animated)
    }

    private func setControlsVisible(_ isVisible: Bool, animated: Bool) {
        areControlsVisible = isVisible

        let updates = {
            self.controlsView.alpha = isVisible ? 1 : 0
            self.progressInfoView.alpha = isVisible ? 1 : 0
            self.closeButton.alpha = isVisible ? 1 : 0
        }

        let completion: (Bool) -> Void = { _ in
            self.controlsView.isUserInteractionEnabled = isVisible
            self.progressInfoView.isUserInteractionEnabled = isVisible
            self.closeButton.isUserInteractionEnabled = isVisible
        }

        if isVisible {
            controlsView.isUserInteractionEnabled = true
            progressInfoView.isUserInteractionEnabled = true
            closeButton.isUserInteractionEnabled = true
        }

        guard animated else {
            updates()
            completion(true)
            return
        }

        UIView.animate(
            withDuration: 0.22,
            delay: 0,
            options: [.beginFromCurrentState, .curveEaseInOut],
            animations: updates,
            completion: completion
        )
    }

    private func seek(to seconds: Double, completion: (() -> Void)? = nil) {
        let time = CMTime(seconds: seconds, preferredTimescale: 600)
        player.seek(to: time, toleranceBefore: .zero, toleranceAfter: .zero) { _ in
            DispatchQueue.main.async {
                completion?()
            }
        }
    }

    private func previewScrubbedProgress(_ progress: CGFloat) {
        let duration = durationSeconds
        guard duration > 0 else {
            progressInfoView.showScrubbingProgress(0, currentTime: "00:00", totalTime: "--:--")
            return
        }

        let normalizedProgress = min(max(Double(progress), 0), 1)
        let target = duration * normalizedProgress
        progressInfoView.showScrubbingProgress(
            CGFloat(normalizedProgress),
            currentTime: formatTime(target),
            totalTime: formatTime(duration)
        )
    }

    private func finishScrubbing(_ progress: CGFloat) {
        let duration = durationSeconds
        guard duration > 0 else {
            isScrubbing = false
            wasPlayingBeforeScrub = false
            progressInfoView.hideScrubbingIndicator()
            updateProgressInfo()
            scheduleControlsAutoHide()
            return
        }

        let normalizedProgress = min(max(Double(progress), 0), 1)
        let target = duration * normalizedProgress
        seek(to: target) { [weak self] in
            guard let self else { return }
            self.isScrubbing = false
            self.progressInfoView.hideScrubbingIndicator()
            self.updateProgressInfo()
            if self.wasPlayingBeforeScrub {
                self.player.play()
                self.controlsView.controllerState = .pause
            }
            self.wasPlayingBeforeScrub = false
            self.scheduleControlsAutoHide()
        }
    }

    private func updateProgressInfo() {
        guard !isScrubbing else {
            return
        }

        let current = currentSeconds
        let duration = durationSeconds
        let progress = duration > 0 ? current / duration : 0
        progressInfoView.configuration = MobileProgressBarInfoConfiguration(
            title: titleText,
            info: videoURL.host ?? videoURL.scheme ?? "Video",
            currentTime: formatTime(current),
            totalTime: duration > 0 ? formatTime(duration) : "--:--",
            progress: CGFloat(progress),
            showsInfo: false
        )
    }

    private var currentSeconds: Double {
        let seconds = player.currentTime().seconds
        return seconds.isFinite ? seconds : 0
    }

    private var durationSeconds: Double {
        let seconds = player.currentItem?.duration.seconds ?? 0
        return seconds.isFinite ? seconds : 0
    }

    private var titleText: String {
        let lastPathComponent = videoURL.lastPathComponent
        return lastPathComponent.isEmpty ? "Internet Video" : lastPathComponent
    }

    private func formatTime(_ seconds: Double) -> String {
        let totalSeconds = max(Int(seconds.rounded()), 0)
        let hours = totalSeconds / 3600
        let minutes = (totalSeconds % 3600) / 60
        let secs = totalSeconds % 60

        if hours > 0 {
            return String(format: "%d:%02d:%02d", hours, minutes, secs)
        }
        return String(format: "%02d:%02d", minutes, secs)
    }
}

extension VideoPlayerViewController: UIGestureRecognizerDelegate {
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        guard areControlsVisible else {
            return true
        }

        let location = touch.location(in: view)
        return !controlsView.frame.contains(location)
            && !progressInfoView.frame.contains(location)
            && !closeButton.frame.contains(location)
    }
}
