import UIKit
import Photos

// MARK: - GalleryViewController

/// Pure UIKit gallery viewer with hero animations, UIScrollView zoom,
/// and hardware-accelerated paging (Telegram parity).
final class GalleryViewController: UIViewController {

    private let state: ImageViewerState
    private weak var sourceView: UIView?
    private let sourceFrame: CGRect

    private let backgroundView = UIView()
    private let pagerView = GalleryPageScrollView()
    private let overlayView = GalleryOverlayView()
    private var dismissController: GalleryDismissController!
    private var heroAnimator: GalleryHeroAnimator!

    private var isDismissing = false
    var onShowInChat: ((String) -> Void)?

    // MARK: - Date Formatters

    private static let timeFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateStyle = .none
        f.timeStyle = .short
        return f
    }()

    private static let dateFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateStyle = .medium
        f.timeStyle = .none
        f.doesRelativeDateFormatting = true
        return f
    }()

    // MARK: - Init

    init(state: ImageViewerState, sourceView: UIView?) {
        self.state = state
        self.sourceView = sourceView
        self.sourceFrame = state.sourceFrame
        super.init(nibName: nil, bundle: nil)
        modalPresentationStyle = .overFullScreen
    }

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

    // MARK: - Status Bar

    override var prefersStatusBarHidden: Bool { true }
    override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { .fade }

    // MARK: - Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .clear

        setupBackground()
        setupPager()
        setupOverlay()
        setupDismiss()

        // Overlay starts hidden — fades in during open animation (Telegram parity: 0.15s linear)
        overlayView.alpha = 0

        heroAnimator = GalleryHeroAnimator(containerView: view, sourceView: sourceView)
        updateOverlayInfo()
        prefetchAdjacentImages(around: state.initialIndex)
    }

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

    // MARK: - Setup

    private func setupBackground() {
        backgroundView.backgroundColor = .black
        backgroundView.alpha = 0
        backgroundView.frame = view.bounds
        backgroundView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(backgroundView)
    }

    private func setupPager() {
        pagerView.frame = view.bounds
        pagerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(pagerView)

        pagerView.imageLoader = { [weak self] index, completion in
            self?.loadImage(at: index, completion: completion)
        }

        pagerView.onSingleTap = { [weak self] in
            self?.toggleControls()
        }

        pagerView.onEdgeTap = { [weak self] dir in
            guard let self else { return }
            let target = self.pagerView.currentPage + dir
            guard target >= 0, target < self.state.images.count else { return }
            self.pagerView.setPage(target, animated: true)
        }

        pagerView.onPageChanged = { [weak self] page in
            self?.updateOverlayInfo()
            self?.prefetchAdjacentImages(around: page)
        }

        pagerView.onZoomChanged = { [weak self] _ in
            _ = self  // Paging already disabled inside GalleryPageScrollView.onZoomChanged
        }

        pagerView.configure(totalPages: state.images.count, initialPage: state.initialIndex)
    }

    private func setupOverlay() {
        overlayView.frame = view.bounds
        overlayView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(overlayView)

        overlayView.onBack = { [weak self] in self?.dismissGallery() }
        overlayView.onMenu = { [weak self] in self?.presentMenuPopup() }
        overlayView.onForward = { [weak self] in _ = self }
        overlayView.onDraw = { [weak self] in _ = self }
        overlayView.onCaptions = { [weak self] in _ = self }
        overlayView.onDelete = { [weak self] in _ = self }
    }

    private func setupDismiss() {
        dismissController = GalleryDismissController()
        view.addGestureRecognizer(dismissController.panGesture)

        dismissController.isZoomedCheck = { [weak self] in
            self?.pagerView.currentZoomPage?.isZoomed ?? false
        }

        dismissController.onDragChanged = { [weak self] translationY in
            self?.handleDragChanged(translationY)
        }

        dismissController.onDismiss = { [weak self] velocity in
            self?.handleDragDismiss(velocity: velocity)
        }

        dismissController.onCancel = { [weak self] in
            self?.handleDragCancel()
        }
    }

    // MARK: - Open Animation

    private func performOpenAnimation() {
        let page = pagerView.currentPage
        guard page < state.images.count else {
            backgroundView.alpha = 1
            overlayView.alpha = 1
            return
        }

        let attachmentId = state.images[page].attachmentId
        if let cachedImage = AttachmentCache.shared.cachedImage(forAttachmentId: attachmentId) {
            let targetFrame = fittedFrame(for: cachedImage.size)
            pagerView.alpha = 0
            heroAnimator.animateOpen(
                image: cachedImage,
                sourceFrame: sourceFrame,
                targetFrame: targetFrame,
                backgroundView: backgroundView,
                contentView: pagerView
            ) { [weak self] in
                self?.pagerView.alpha = 1
            }
            // Overlay controls fade in after hero animation settles (smooth appearance)
            UIView.animate(withDuration: 0.3, delay: 0.2, options: .curveEaseIn) {
                self.overlayView.alpha = 1
            }
        } else {
            // No cached image — simple fade (overlay fades in with background)
            UIView.animate(withDuration: 0.25) {
                self.backgroundView.alpha = 1
                self.overlayView.alpha = 1
            }
        }
    }

    // MARK: - Dismiss

    private func dismissGallery() {
        guard !isDismissing else { return }
        isDismissing = true

        let page = pagerView.currentPage
        guard page < state.images.count else {
            finishDismiss()
            return
        }

        let attachmentId = state.images[page].attachmentId
        if let image = AttachmentCache.shared.cachedImage(forAttachmentId: attachmentId) {
            let currentFrame = fittedFrame(for: image.size)
            overlayView.setControlsVisible(false, animated: true)
            heroAnimator.animateClose(
                image: image,
                currentFrame: currentFrame,
                backgroundView: backgroundView,
                contentView: pagerView
            ) { [weak self] in
                self?.finishDismiss()
            }
        } else {
            heroAnimator.animateFadeDismiss(
                backgroundView: backgroundView,
                contentView: pagerView
            ) { [weak self] in
                self?.finishDismiss()
            }
        }
    }

    private func finishDismiss() {
        heroAnimator.restoreSourceView()
        dismiss(animated: false)
    }

    // MARK: - Drag-to-Dismiss

    private func handleDragChanged(_ translationY: CGFloat) {
        let progress = min(abs(translationY) / 300, 1.0)
        backgroundView.alpha = 1 - progress
        overlayView.alpha = 1 - min(abs(translationY) / 150, 1.0)

        // Move current page
        pagerView.transform = CGAffineTransform(translationX: 0, y: translationY)
    }

    private func handleDragDismiss(velocity: CGFloat) {
        guard !isDismissing else { return }
        isDismissing = true

        let page = pagerView.currentPage
        guard page < state.images.count else {
            finishDismiss()
            return
        }

        let attachmentId = state.images[page].attachmentId
        if let image = AttachmentCache.shared.cachedImage(forAttachmentId: attachmentId),
           sourceView?.window != nil {
            let currentFrame = fittedFrame(for: image.size)
                .offsetBy(dx: 0, dy: pagerView.transform.ty)
            pagerView.transform = .identity
            overlayView.setControlsVisible(false, animated: false)
            heroAnimator.animateClose(
                image: image,
                currentFrame: currentFrame,
                backgroundView: backgroundView,
                contentView: pagerView
            ) { [weak self] in
                self?.finishDismiss()
            }
        } else {
            let direction: CGFloat = velocity > 0 ? 1 : -1
            UIView.animate(withDuration: 0.2) {
                self.pagerView.transform = CGAffineTransform(translationX: 0, y: direction * self.view.bounds.height)
                self.backgroundView.alpha = 0
                self.overlayView.alpha = 0
            } completion: { _ in
                self.pagerView.transform = .identity
                self.finishDismiss()
            }
        }
    }

    private func handleDragCancel() {
        UIView.animate(
            withDuration: 0.35,
            delay: 0,
            usingSpringWithDamping: 0.8,
            initialSpringVelocity: 0,
            options: .curveEaseInOut
        ) {
            self.pagerView.transform = .identity
            self.backgroundView.alpha = 1
            self.overlayView.alpha = 1
        }
    }

    // MARK: - Controls

    private func toggleControls() {
        let visible = !overlayView.isControlsVisible
        overlayView.setControlsVisible(visible, animated: true)
    }

    // MARK: - Overlay Info

    private func updateOverlayInfo() {
        let page = pagerView.currentPage
        guard page < state.images.count else { return }
        let info = state.images[page]
        let day = Self.dateFormatter.string(from: info.timestamp)
        let time = Self.timeFormatter.string(from: info.timestamp)
        let counter = "\(page + 1) of \(state.images.count)"

        let rawCaption = info.caption
        let caption = rawCaption.isEmpty || MessageCellLayout.isGarbageOrEncrypted(rawCaption) ? "" : rawCaption

        overlayView.update(
            title: info.senderName,
            subtitle: "\(day) at \(time)",
            counter: counter,
            showCounter: state.images.count > 1,
            caption: caption
        )
    }

    // MARK: - Image Loading

    private func loadImage(at index: Int, completion: @escaping (UIImage?) -> Void) {
        guard index >= 0, index < state.images.count else {
            completion(nil)
            return
        }
        let attachmentId = state.images[index].attachmentId

        if let cached = AttachmentCache.shared.cachedImage(forAttachmentId: attachmentId) {
            completion(cached)
            return
        }

        Task.detached(priority: .userInitiated) {
            await ImageLoadLimiter.shared.acquire()
            let loaded = AttachmentCache.shared.loadImage(forAttachmentId: attachmentId)
            await ImageLoadLimiter.shared.release()

            if let loaded {
                await MainActor.run { completion(loaded) }
            } else {
                // Retry once after 500ms — covers transient I/O and key-race errors
                try? await Task.sleep(nanoseconds: 500_000_000)
                await ImageLoadLimiter.shared.acquire()
                let retried = AttachmentCache.shared.loadImage(forAttachmentId: attachmentId)
                await ImageLoadLimiter.shared.release()
                await MainActor.run { completion(retried) }
            }
        }
    }

    private func prefetchAdjacentImages(around index: Int) {
        for offset in [-2, -1, 1, 2] {
            let i = index + offset
            guard i >= 0, i < state.images.count else { continue }
            let aid = state.images[i].attachmentId
            guard AttachmentCache.shared.cachedImage(forAttachmentId: aid) == nil else { continue }
            Task.detached(priority: .utility) {
                await ImageLoadLimiter.shared.acquire()
                _ = AttachmentCache.shared.loadImage(forAttachmentId: aid)
                await ImageLoadLimiter.shared.release()
            }
        }
    }

    // MARK: - Actions

    private func saveCurrentImage() {
        let page = pagerView.currentPage
        guard page < state.images.count,
              let image = AttachmentCache.shared.loadImage(forAttachmentId: state.images[page].attachmentId)
        else { return }
        PHPhotoLibrary.requestAuthorization(for: .addOnly) { status in
            guard status == .authorized || status == .limited else { return }
            UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
        }
    }

    // MARK: - Menu Popup

    private func presentMenuPopup() {
        let items: [TelegramContextMenuItem] = [
            TelegramContextMenuItem(
                title: "Show in Chat",
                iconName: "bubble.left.and.text.bubble.right",
                isDestructive: false,
                handler: { [weak self] in
                    guard let self else { return }
                    let page = self.pagerView.currentPage
                    if page < self.state.images.count {
                        self.onShowInChat?(self.state.images[page].messageId)
                    }
                    self.dismissGallery()
                }
            ),
            TelegramContextMenuItem(
                title: "Save Image",
                iconName: "square.and.arrow.down",
                isDestructive: false,
                handler: { [weak self] in self?.saveCurrentImage() }
            ),
            TelegramContextMenuItem(
                title: "Delete",
                iconName: "trash",
                isDestructive: true,
                handler: { [weak self] in _ = self }
            ),
        ]
        let anchorFrame = overlayView.menuButtonFrame(in: view)
        GalleryMenuPopup.present(in: view, anchorFrame: anchorFrame, items: items)
    }

    // MARK: - Geometry

    private func fittedFrame(for imageSize: CGSize) -> CGRect {
        let viewSize = view.bounds.size
        guard imageSize.width > 0, imageSize.height > 0,
              viewSize.width > 0, viewSize.height > 0 else {
            return view.bounds
        }
        let scale = min(viewSize.width / imageSize.width, viewSize.height / imageSize.height)
        let w = imageSize.width * scale
        let h = imageSize.height * scale
        return CGRect(x: (viewSize.width - w) / 2, y: (viewSize.height - h) / 2, width: w, height: h)
    }
}
