import 'dart:io';

import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';

import '../../../../models/attachment.dart';
import '../../photo_viewer.dart';
import '../photo_hero.dart';
import 'bubble_context.dart';

class PhotoBubble extends StatelessWidget {
  static const Radius _bigRadius = Radius.circular(
    BubbleContext.bubbleBorderRadius,
  );
  static const Radius _smallRadius = Radius.circular(4);
  static const Radius _photoRadius = Radius.circular(
    BubbleContext.photoBorderRadius,
  );

  final BubbleContext ctx;
  final List<PhotoAttachment> photos;
  final Widget? caption;
  final bool hasContentAbove;

  const PhotoBubble({
    super.key,
    required this.ctx,
    required this.photos,
    this.caption,
    this.hasContentAbove = false,
  });

  static double layoutWidth(List<PhotoAttachment> photos) {
    if (photos.length != 1) return BubbleContext.photoMaxSize;
    return (photos.single.width?.toDouble() ?? 200).clamp(
      BubbleContext.photoMinSize,
      BubbleContext.photoMaxSize,
    );
  }

  @override
  Widget build(BuildContext context) {
    final message = ctx.message;
    final hasMessageCaption = message.text != null && message.text!.isNotEmpty;
    final resolvedCaption =
        caption ?? (hasMessageCaption ? ctx.caption() : null);
    final hasCaption = resolvedCaption != null;
    final count = photos.length;

    Widget photosWidget;
    if (count == 1) {
      photosWidget = _buildSinglePhoto(
        ctx,
        photos[0],
        hasCaption: hasCaption,
        hasContentAbove: hasContentAbove,
      );
    } else if (count == 2) {
      photosWidget = _buildTwoPhotos(ctx, photos[0], photos[1]);
    } else {
      photosWidget = _buildPhotoGrid(ctx, photos);
    }

    if (!hasCaption) {
      return Stack(
        children: [
          photosWidget,
          Positioned(
            bottom: BubbleContext.compactTimePadding,
            right: BubbleContext.compactTimePadding,
            child: ctx.compactTime(),
          ),
        ],
      );
    }

    if (count == 1) {
      return SizedBox(
        width: layoutWidth(photos),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            photosWidget,
            Padding(
              padding: const EdgeInsets.only(
                left: BubbleContext.captionPaddingHorizontal,
                right: BubbleContext.captionPaddingRight,
                bottom: 6,
              ),
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.end,
                children: [
                  Expanded(child: resolvedCaption),
                  ctx.meta(),
                ],
              ),
            ),
          ],
        ),
      );
    }

    return Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        photosWidget,
        Padding(
          padding: const EdgeInsets.only(
            left: BubbleContext.captionPaddingHorizontal,
            right: BubbleContext.captionPaddingRight,
            bottom: 6,
          ),
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.end,
            children: [
              Expanded(child: resolvedCaption),
              ctx.meta(),
            ],
          ),
        ),
      ],
    );
  }

  Widget _buildSinglePhoto(
    BubbleContext ctx,
    PhotoAttachment photo, {
    required bool hasCaption,
    required bool hasContentAbove,
  }) {
    final width = photo.width?.toDouble() ?? 200;
    final height = photo.height?.toDouble() ?? 200;

    final constrainedWidth = width.clamp(
      BubbleContext.photoMinSize,
      BubbleContext.photoMaxSize,
    );
    final constrainedHeight = height.clamp(
      BubbleContext.photoMinSize,
      BubbleContext.photoMaxSize,
    );
    final dpr = MediaQuery.of(ctx.context).devicePixelRatio;

    final matchTop = hasCaption && !hasContentAbove;
    final matchBottom = !hasCaption;

    final topR = matchTop ? _bigRadius : _photoRadius;
    final bottomL = matchBottom
        ? (ctx.isMe ? _bigRadius : _smallRadius)
        : _smallRadius;
    final bottomR = matchBottom
        ? (ctx.isMe ? _smallRadius : _bigRadius)
        : _smallRadius;

    final radius = BorderRadius.only(
      topLeft: topR,
      topRight: topR,
      bottomLeft: bottomL,
      bottomRight: bottomR,
    );
    final memWidth = (constrainedWidth * dpr).round();
    final memHeight = (constrainedHeight * dpr).round();

    return ClipRRect(
      borderRadius: radius,
      child: Stack(
        children: [
          _buildPhotoImage(
            ctx,
            photo,
            constrainedWidth,
            constrainedHeight,
            memWidth: memWidth,
            memHeight: memHeight,
          ),
          if (ctx.uploadProgress != null)
            _buildUploadOverlay(ctx.uploadProgress!, 0),
          if (ctx.uploadProgress == null)
            Positioned.fill(
              child: Builder(
                builder: (tileContext) => GestureDetector(
                  behavior: HitTestBehavior.opaque,
                  onTap: () => _openPhotoViewer(
                    ctx.context,
                    0,
                    tileContext: tileContext,
                    radius: radius,
                    memWidth: memWidth,
                    memHeight: memHeight,
                  ),
                ),
              ),
            ),
        ],
      ),
    );
  }

  Widget _buildPhotoImage(
    BubbleContext ctx,
    PhotoAttachment photo,
    double width,
    double height, {
    required int memWidth,
    required int memHeight,
  }) {
    final localPath = photo.localPath;
    if (localPath != null) {
      return Image.file(
        File(localPath),
        width: width,
        height: height,
        fit: BoxFit.cover,
        cacheWidth: memWidth,
        gaplessPlayback: true,
        errorBuilder: (_, _, _) =>
            _buildPhotoPlaceholder(ctx.cs, width, height),
      );
    }
    final imageUrl = photo.baseUrl ?? '';
    if (imageUrl.isNotEmpty) {
      return CachedNetworkImage(
        imageUrl: imageUrl,
        width: width,
        height: height,
        fit: BoxFit.cover,
        memCacheWidth: memWidth,
        memCacheHeight: memHeight,
        fadeInDuration: Duration.zero,
        placeholderFadeInDuration: Duration.zero,
        errorWidget: (_, _, _) => _buildPhotoPlaceholder(ctx.cs, width, height),
      );
    }
    return _buildPhotoPlaceholder(ctx.cs, width, height);
  }

  Widget _buildUploadOverlay(
    ValueListenable<List<double>> progress,
    int index,
  ) {
    return Positioned.fill(
      child: ValueListenableBuilder<List<double>>(
        valueListenable: progress,
        builder: (context, values, _) {
          final value = index < values.length ? values[index] : 1.0;
          final indeterminate = value <= 0 || value >= 1.0;
          return Container(
            color: Colors.black.withValues(alpha: 0.4),
            alignment: Alignment.center,
            child: SizedBox(
              width: 34,
              height: 34,
              child: CircularProgressIndicator(
                strokeWidth: 2.5,
                value: indeterminate ? null : value,
                color: Colors.white,
              ),
            ),
          );
        },
      ),
    );
  }

  BorderRadius _multiPhotoCornerRadius({
    required bool matchTop,
    required bool matchBottom,
    required bool isMe,
  }) {
    final topR = matchTop ? _bigRadius : _photoRadius;
    final bottomL = matchBottom ? _smallRadius : _photoRadius;
    final bottomR = matchBottom
        ? (isMe ? _smallRadius : _bigRadius)
        : _photoRadius;
    return BorderRadius.only(
      topLeft: topR,
      topRight: topR,
      bottomLeft: bottomL,
      bottomRight: bottomR,
    );
  }

  Widget _buildTwoPhotos(
    BubbleContext ctx,
    PhotoAttachment p1,
    PhotoAttachment p2,
  ) {
    final matchTop =
        ctx.hasMultiplePhotosNoCaption && ctx.shape == BubbleShape.singleTop;
    final matchBottom =
        ctx.hasMultiplePhotosNoCaption && ctx.shape == BubbleShape.singleBottom;

    return ClipRRect(
      borderRadius: _multiPhotoCornerRadius(
        matchTop: matchTop,
        matchBottom: matchBottom,
        isMe: ctx.isMe,
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Expanded(child: _buildPhotoTile(ctx, p1, 0)),
          const SizedBox(width: 2),
          Expanded(child: _buildPhotoTile(ctx, p2, 1)),
        ],
      ),
    );
  }

  Widget _buildPhotoGrid(BubbleContext ctx, List<PhotoAttachment> photos) {
    final displayCount = photos.length > 4 ? 4 : photos.length;
    final remaining = photos.length - 4;

    final matchTop =
        ctx.hasMultiplePhotosNoCaption && ctx.shape == BubbleShape.singleTop;
    final matchBottom =
        ctx.hasMultiplePhotosNoCaption && ctx.shape == BubbleShape.singleBottom;

    final rows = <Widget>[];
    for (var i = 0; i < displayCount; i += 2) {
      if (rows.isNotEmpty) rows.add(const SizedBox(height: 2));
      rows.add(
        Row(
          children: [
            Expanded(child: _buildGridTile(ctx, photos, i, remaining)),
            const SizedBox(width: 2),
            Expanded(
              child: i + 1 < displayCount
                  ? _buildGridTile(ctx, photos, i + 1, remaining)
                  : const SizedBox.shrink(),
            ),
          ],
        ),
      );
    }

    return ClipRRect(
      borderRadius: _multiPhotoCornerRadius(
        matchTop: matchTop,
        matchBottom: matchBottom,
        isMe: ctx.isMe,
      ),
      child: Column(mainAxisSize: MainAxisSize.min, children: rows),
    );
  }

  Widget _buildGridTile(
    BubbleContext ctx,
    List<PhotoAttachment> photos,
    int index,
    int remaining,
  ) {
    if (index == 3 && remaining > 0) {
      return _buildPhotoTileWithOverlay(
        ctx,
        photos[index],
        '+$remaining',
        index,
      );
    }
    return _buildPhotoTile(ctx, photos[index], index);
  }

  Widget _buildPhotoTile(BubbleContext ctx, PhotoAttachment photo, int index) {
    final cachePx =
        (BubbleContext.photoMaxSize /
                2 *
                MediaQuery.of(ctx.context).devicePixelRatio)
            .round();
    return AspectRatio(
      aspectRatio: 1,
      child: Stack(
        children: [
          _buildPhotoImage(
            ctx,
            photo,
            double.infinity,
            double.infinity,
            memWidth: cachePx,
            memHeight: cachePx,
          ),
          if (ctx.uploadProgress != null)
            _buildUploadOverlay(ctx.uploadProgress!, index),
          if (ctx.uploadProgress == null)
            _buildTileTapTarget(ctx, index, cachePx),
        ],
      ),
    );
  }

  Widget _buildTileTapTarget(BubbleContext ctx, int index, int cachePx) {
    return Positioned.fill(
      child: Builder(
        builder: (tileContext) => GestureDetector(
          behavior: HitTestBehavior.opaque,
          onTap: () => _openPhotoViewer(
            ctx.context,
            index,
            tileContext: tileContext,
            radius: BorderRadius.zero,
            memWidth: cachePx,
            memHeight: cachePx,
          ),
        ),
      ),
    );
  }

  Widget _buildPhotoTileWithOverlay(
    BubbleContext ctx,
    PhotoAttachment photo,
    String overlay,
    int index,
  ) {
    final cachePx =
        (BubbleContext.photoMaxSize /
                2 *
                MediaQuery.of(ctx.context).devicePixelRatio)
            .round();
    return AspectRatio(
      aspectRatio: 1,
      child: Stack(
        children: [
          _buildPhotoImage(
            ctx,
            photo,
            double.infinity,
            double.infinity,
            memWidth: cachePx,
            memHeight: cachePx,
          ),
          Positioned.fill(
            child: Container(
              color: Colors.black45,
              child: Center(
                child: Text(
                  overlay,
                  style: const TextStyle(
                    color: Colors.white,
                    fontSize: 24,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ),
          ),
          if (ctx.uploadProgress != null)
            _buildUploadOverlay(ctx.uploadProgress!, index),
          if (ctx.uploadProgress == null)
            _buildTileTapTarget(ctx, index, cachePx),
        ],
      ),
    );
  }

  Widget _buildPhotoPlaceholder(
    ColorScheme cs,
    double w,
    double h, {
    VoidCallback? onRetry,
  }) {
    return Container(
      width: w,
      height: h,
      color: cs.surfaceContainerHighest,
      child: onRetry != null
          ? Center(
              child: IconButton(
                icon: Icon(Symbols.refresh, color: cs.onSurfaceVariant),
                onPressed: onRetry,
                tooltip: 'Retry',
              ),
            )
          : Center(
              child: Icon(Symbols.image, size: 48, color: cs.onSurfaceVariant),
            ),
    );
  }

  static ImageProvider? _photoProvider(
    PhotoAttachment photo, {
    required int memWidth,
    required int memHeight,
  }) {
    final localPath = photo.localPath;
    if (localPath != null) {
      return ResizeImage.resizeIfNeeded(
        memWidth,
        null,
        FileImage(File(localPath)),
      );
    }
    final url = photo.baseUrl ?? '';
    if (url.isEmpty) return null;
    return ResizeImage.resizeIfNeeded(
      memWidth,
      memHeight,
      CachedNetworkImageProvider(url),
    );
  }

  static Size? _photoSize(PhotoAttachment photo) {
    final width = photo.width ?? 0;
    final height = photo.height ?? 0;
    if (width <= 0 || height <= 0) return null;
    return Size(width.toDouble(), height.toDouble());
  }

  void _openPhotoViewer(
    BuildContext context,
    int index, {
    required BuildContext tileContext,
    required BorderRadius radius,
    required int memWidth,
    required int memHeight,
  }) {
    final photo = photos[index];
    final hero = PhotoHeroController(
      origin: () => photoHeroRectOf(tileContext),
      image: _photoProvider(photo, memWidth: memWidth, memHeight: memHeight),
      size: _photoSize(photo),
      radius: radius,
    );
    Navigator.of(context).push(
      PhotoHeroRoute<void>(
        hero: hero,
        builder: (_) => PhotoViewerScreen(
          photos: photos,
          initialIndex: index,
          chatId: ctx.chatId,
          message: ctx.message,
          actions: ctx.photoActions,
          hero: hero,
          sourceName: ctx.chatName,
        ),
      ),
    );
  }
}
