import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:local_auth/local_auth.dart';
import 'package:mobile_scanner/mobile_scanner.dart';

import 'instance.dart';
import 'session.dart';

/// The WebView that is the app.
///
/// Everything below is about one idea: **every bridge is an exported native
/// capability**. A handler the page can call is a function anyone who gets
/// script into the page can call, so each one is narrow, each one is checked
/// natively, and none of them returns a decision the page is trusted to honour.
class PortalShell extends StatefulWidget {
  const PortalShell({super.key, required this.session});

  final Session session;

  @override
  State<PortalShell> createState() => _PortalShellState();
}

class _PortalShellState extends State<PortalShell> {
  InAppWebViewController? _controller;
  bool _loading = true;
  bool _offline = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Stack(
          children: [
            InAppWebView(
              initialUrlRequest: URLRequest(url: WebUri.uri(Instance.sessionExchange)),
              initialSettings: InAppWebViewSettings(
                // The transfer-proof upload depends on this being a webview
                // that can open a file chooser at all.
                useOnDownloadStart: true,
                javaScriptEnabled: true,
                supportZoom: false,
                // No third-party content of any kind: the portal loads its own
                // assets and Google Fonts, and nothing else has business here.
                mediaPlaybackRequiresUserGesture: false,
                allowsInlineMediaPlayback: true,
                transparentBackground: true,
                // Cookies belong to this app and to no other app's webview.
                incognito: false,
                clearCache: false,
              ),
              onWebViewCreated: _registerBridges,
              shouldOverrideUrlLoading: _gateNavigation,
              onLoadStart: (_, __) => setState(() => _loading = true),
              onLoadStop: (_, __) => setState(() {
                _loading = false;
                _offline = false;
              }),
              onReceivedError: (_, __, ___) => setState(() {
                _loading = false;
                _offline = true;
              }),
            ),
            if (_loading) const Center(child: CircularProgressIndicator()),
            if (_offline) _OfflineNotice(onRetry: _reload),
          ],
        ),
      ),
    );
  }

  Future<void> _reload() async {
    setState(() {
      _offline = false;
      _loading = true;
    });

    await _controller?.loadUrl(urlRequest: URLRequest(url: WebUri.uri(Instance.sessionExchange)));
  }

  /// Only this academy's host renders inside the app.
  ///
  /// The origin comes from the request the controller is about to make, read
  /// natively. Anything else — a link in a news article, a payment provider, a
  /// phishing page — opens in the system browser, outside this webview's
  /// cookie jar and outside every bridge below.
  Future<NavigationActionPolicy> _gateNavigation(
    InAppWebViewController controller,
    NavigationAction action,
  ) async {
    final url = action.request.url;

    if (url == null) return NavigationActionPolicy.CANCEL;

    if (url.scheme == 'tel' || url.scheme == 'mailto' || url.scheme == 'whatsapp') {
      await launchExternally(url);
      return NavigationActionPolicy.CANCEL;
    }

    if (url.host != Instance.allowedHost) {
      await launchExternally(url);
      return NavigationActionPolicy.CANCEL;
    }

    return NavigationActionPolicy.ALLOW;
  }

  Future<void> launchExternally(WebUri url) async {
    await ChromeSafariBrowser().open(url: url);
  }

  void _registerBridges(InAppWebViewController controller) {
    _controller = controller;

    /// QR scanning happens natively and hands back only the decoded string.
    /// The page never gets camera access, and the shell never gets a chance to
    /// interpret what it scanned.
    controller.addJavaScriptHandler(
      handlerName: 'scanQr',
      callback: (_) async {
        if (!await _originIsOurs()) return null;

        final result = await Navigator.of(context).push<String>(
          MaterialPageRoute(builder: (_) => const _ScannerScreen()),
        );

        return result;
      },
    );

    /// Biometrics gate a native action and return nothing the page can use as
    /// an authorisation decision.
    ///
    /// A handler that returned `true` for "the user authenticated" would be a
    /// boolean anyone with script in the page could fabricate. This one
    /// re-exchanges the stored token for a fresh session — work only native
    /// code can do, because only native code holds the token.
    controller.addJavaScriptHandler(
      handlerName: 'unlockSession',
      callback: (_) async {
        if (!await _originIsOurs()) return null;

        final auth = LocalAuthentication();

        if (!await auth.isDeviceSupported()) return null;

        final ok = await auth.authenticate(
          localizedReason: 'تأكيد هويتك للدخول إلى حسابك',
          options: const AuthenticationOptions(biometricOnly: false, stickyAuth: true),
        );

        if (!ok) return null;

        await controller.loadUrl(urlRequest: URLRequest(url: WebUri.uri(Instance.sessionExchange)));

        return null;
      },
    );

    controller.addJavaScriptHandler(
      handlerName: 'signOut',
      callback: (_) async {
        if (!await _originIsOurs()) return null;

        await widget.session.signOut();
        await CookieManager.instance().deleteAllCookies();

        if (mounted) Navigator.of(context).pushReplacementNamed('/sign-in');

        return null;
      },
    );
  }

  /// Read the origin from the controller, never from the page.
  ///
  /// A page can say anything about itself; the controller knows what it
  /// actually loaded.
  Future<bool> _originIsOurs() async {
    final current = await _controller?.getUrl();

    return current != null && current.host == Instance.allowedHost;
  }
}

class _OfflineNotice extends StatelessWidget {
  const _OfflineNotice({required this.onRetry});

  final VoidCallback onRetry;

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Theme.of(context).scaffoldBackgroundColor,
      alignment: Alignment.center,
      padding: const EdgeInsets.all(24),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          const Icon(Icons.wifi_off, size: 48),
          const SizedBox(height: 12),
          const Text('لا يوجد اتصال بالإنترنت', textAlign: TextAlign.center),
          const SizedBox(height: 16),
          FilledButton(onPressed: onRetry, child: const Text('إعادة المحاولة')),
        ],
      ),
    );
  }
}

/// Native QR. The decoded string is passed back over the channel and nothing
/// else crosses the boundary.
class _ScannerScreen extends StatelessWidget {
  const _ScannerScreen();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('مسح الرمز')),
      body: MobileScanner(
        onDetect: (capture) {
          final value = capture.barcodes.firstOrNull?.rawValue;

          if (value != null) Navigator.of(context).pop(value);
        },
      ),
    );
  }
}
