import 'dart:async';
import 'dart:io';
import 'dart:ui';

import 'package:flutter/foundation.dart';
import 'package:screen_retriever/screen_retriever.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:window_manager/window_manager.dart';

import '../../../shared/utils/app_logger.dart';


class PipManager {
  static const _tag = 'Pip';
  static const double _aspectRatio = 16 / 9;
  static const Size _minPipSize = Size(360, 202.5);
  static const Size _defaultPipSize = Size(480, 270);
  static const Size _maxPipSize = Size(960, 540);
  static const Size _unboundedMaximumSize = Size(100000, 100000);
  static const double _edgeMargin = 24;

  static const _keyPipX = 'smplayer.pip_last_x';
  static const _keyPipY = 'smplayer.pip_last_y';
  static const _keyPipWidth = 'smplayer.pip_last_width';
  static const _keyPipHeight = 'smplayer.pip_last_height';
  static const _keyPipAlwaysOnTop = 'smplayer.pip_always_on_top';

  PipManager({required this._onStateChanged});

  final VoidCallback _onStateChanged;

  bool _isPip = false;
  bool _isChanging = false;
  bool _isDisposed = false;
  Rect? _windowBoundsBefore;
  bool _wasMaximized = false;
  bool _wasResizable = true;
  bool _wasAlwaysOnTop = false;
  Future<void>? _disposeRestoreFuture;
  Rect? _lastPipBounds;


  bool _pipAlwaysOnTop = true;


  static bool isActive = false;

  bool get isPip => _isPip;


  bool get isAlwaysOnTop => _pipAlwaysOnTop;

  Future<void> toggle() async {
    if (_isDisposed) return;
    if (_isChanging) return;
    if (_isPip) {
      await leave();
    } else {
      await enter();
    }
  }

  Future<void> enter() async {
    if (_isDisposed) return;
    if (_isPip || _isChanging) return;
    _isChanging = true;
    try {
      _windowBoundsBefore = await windowManager.getBounds();
      _wasMaximized = await windowManager.isMaximized();
      _wasResizable = await windowManager.isResizable();
      _wasAlwaysOnTop = await windowManager.isAlwaysOnTop();
      _pipAlwaysOnTop = await _loadPersistedAlwaysOnTop();
      final display = await _tryPrimaryDisplay();

      if (_wasMaximized) {
        await windowManager.unmaximize();
      }

      await windowManager.setMinimumSize(_minPipSize);
      await windowManager.setMaximumSize(
        display == null ? _maxPipSize : pipMaximumSizeForDisplay(display),
      );
      await windowManager.setAspectRatio(_aspectRatio);
      await windowManager.setResizable(true);
      await windowManager.setAlwaysOnTop(_pipAlwaysOnTop);

      final lastBounds = _lastPipBounds ?? await _loadPersistedPipBounds();
      final pipSize = lastBounds != null
          ? Size(lastBounds.width, lastBounds.height)
          : _defaultPipSize;
      final bounds =
          lastBounds ??
          (display == null
              ? _fallbackPipBounds(pipSize)
              : pipBoundsForDisplay(display, pipSize));
      await windowManager.setBounds(bounds);
      await windowManager.focus();

      _setPipState(true);
      if (_isDisposed) {
        _scheduleDisposeRestore();
      }
    } catch (error, stack) {
      AppLogger.warn(_tag, 'Failed to enter pip', error);
      AppLogger.debug(_tag, stack.toString());
      try {
        await _restoreWindowState(updateState: false);
      } catch (_) {}
    } finally {
      _isChanging = false;
    }
  }

  Future<void> leave({bool updateState = true}) async {
    if (_isDisposed && updateState) return;
    if (!_isPip) return;
    if (_isChanging) {
      return;
    }
    _isChanging = true;
    try {
      await _restoreWindowState(updateState: updateState);
    } finally {
      _isChanging = false;
    }
  }


  Future<void> toggleAlwaysOnTop() => setAlwaysOnTop(!_pipAlwaysOnTop);

  Future<void> setAlwaysOnTop(bool value) async {
    if (_isDisposed) return;
    if (!_isPip) return;
    if (_pipAlwaysOnTop == value) return;
    if (_isChanging) return;
    _isChanging = true;
    try {
      if (_isDisposed || !_isPip) return;
      await windowManager.setAlwaysOnTop(value);
      _pipAlwaysOnTop = value;
      await _persistAlwaysOnTop(value);
      if (!_isDisposed) {
        _onStateChanged();
      }
    } catch (error) {
      AppLogger.warn(_tag, 'Failed to set pip always-on-top', error);
    } finally {
      _isChanging = false;
    }
  }

  void dispose() {
    if (_isDisposed) return;
    _isDisposed = true;
    if (_isPip) {
      _scheduleDisposeRestore();
    }
  }

  @visibleForTesting
  Future<void>? get disposeRestoreFuture => _disposeRestoreFuture;

  void _scheduleDisposeRestore() {
    if (_disposeRestoreFuture != null) return;
    final restore = _restoreAfterDispose();
    _disposeRestoreFuture = restore;
    unawaited(restore);
  }

  Future<void> _restoreAfterDispose() async {
    while (_isChanging) {
      await Future<void>.delayed(Duration.zero);
    }
    if (!_isPip) return;

    _isChanging = true;
    try {
      await _restoreWindowState(updateState: false);
    } finally {
      _isChanging = false;
    }
  }

  Future<void> _restoreWindowState({required bool updateState}) async {
    try {
      final bounds = await windowManager.getBounds();
      _lastPipBounds = bounds;
      unawaited(_persistPipBounds(bounds));
    } catch (_) {}
    try {
      await windowManager.setAspectRatio(-1);
      await windowManager.setMaximumSize(_unboundedMaximumSize);

      await windowManager.setMinimumSize(const Size(960, 640));
      await windowManager.setAlwaysOnTop(_wasAlwaysOnTop);
      await windowManager.setResizable(_wasResizable);

      if (_wasMaximized) {
        await windowManager.maximize();
      } else {
        final bounds = _windowBoundsBefore;
        if (bounds != null) {
          await windowManager.setBounds(bounds);
        }
      }
      await windowManager.focus();
    } catch (error) {
      AppLogger.warn(_tag, 'Failed to restore window', error);
    }

    _setPipState(false);
  }

  void _setPipState(bool pip) {
    if (_isPip == pip) return;
    _isPip = pip;
    PipManager.isActive = pip;
    if (!_isDisposed) {
      _onStateChanged();
    }
  }

  Future<Display?> _tryPrimaryDisplay() async {
    try {
      return await screenRetriever.getPrimaryDisplay();
    } catch (error) {
      AppLogger.warn(_tag, 'Failed to read primary display', error);
      return null;
    }
  }

  Rect _fallbackPipBounds(Size size) {
    final fallback =
        _windowBoundsBefore ?? Rect.fromLTWH(0, 0, size.width, size.height);
    return Rect.fromLTWH(fallback.left, fallback.top, size.width, size.height);
  }

  @visibleForTesting
  static Rect pipBoundsForDisplay(Display display, Size size) {
    final visibleRect = _visibleRectForDisplay(display);
    final fitted = _fitSizeToVisibleArea(size, visibleRect.size);
    var left = visibleRect.right - fitted.width - _edgeMargin;
    final top = visibleRect.bottom - fitted.height - _edgeMargin;

    if (Platform.isWindows) {
      const border = 8.0;
      left -= border;
    }

    return Rect.fromLTWH(left, top, fitted.width, fitted.height);
  }

  @visibleForTesting
  static Size pipMaximumSizeForDisplay(Display display) {
    final visibleRect = _visibleRectForDisplay(display);
    final fitted = _fitSizeToVisibleArea(_maxPipSize, visibleRect.size);
    if (fitted.width < _minPipSize.width ||
        fitted.height < _minPipSize.height) {
      return _minPipSize;
    }
    return fitted;
  }

  static Rect _visibleRectForDisplay(Display display) {
    final visiblePos = display.visiblePosition ?? Offset.zero;
    final visibleSize = display.visibleSize ?? display.size;
    return visiblePos & visibleSize;
  }

  static Size _fitSizeToVisibleArea(Size preferred, Size visibleSize) {
    final maxWidth = visibleSize.width - _edgeMargin * 2;
    final maxHeight = visibleSize.height - _edgeMargin * 2;
    if (maxWidth <= 0 || maxHeight <= 0) return preferred;

    var width = preferred.width;
    var height = preferred.height;
    if (width > maxWidth) {
      width = maxWidth;
      height = width / _aspectRatio;
    }
    if (height > maxHeight) {
      height = maxHeight;
      width = height * _aspectRatio;
    }
    return Size(width, height);
  }

  Future<Rect?> _loadPersistedPipBounds() async {
    try {
      final p = await SharedPreferences.getInstance();
      final x = p.getDouble(_keyPipX);
      final y = p.getDouble(_keyPipY);
      final w = p.getDouble(_keyPipWidth);
      final h = p.getDouble(_keyPipHeight);
      if (x == null || y == null || w == null || h == null) return null;
      if (w <= 0 || h <= 0) return null;
      return Rect.fromLTWH(x, y, w, h);
    } catch (_) {
      return null;
    }
  }

  Future<void> _persistPipBounds(Rect bounds) async {
    try {
      final p = await SharedPreferences.getInstance();
      await Future.wait([
        p.setDouble(_keyPipX, bounds.left),
        p.setDouble(_keyPipY, bounds.top),
        p.setDouble(_keyPipWidth, bounds.width),
        p.setDouble(_keyPipHeight, bounds.height),
      ]);
    } catch (_) {}
  }

  Future<bool> _loadPersistedAlwaysOnTop() async {
    try {
      final p = await SharedPreferences.getInstance();
      return p.getBool(_keyPipAlwaysOnTop) ?? true;
    } catch (_) {
      return true;
    }
  }

  Future<void> _persistAlwaysOnTop(bool value) async {
    try {
      final p = await SharedPreferences.getInstance();
      await p.setBool(_keyPipAlwaysOnTop, value);
    } catch (_) {}
  }
}
