import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:pointycastle/impl.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
import 'package:sqflite/sqflite.dart';
import 'package:uuid/uuid.dart';
import 'package:http/http.dart' as http;

import '/models/friends.dart';
import '/models/my_profile.dart';
import '/utils/encryption/aes_helper.dart';
import '/utils/encryption/crypto_utils.dart';
import '/utils/storage/database.dart';
import '/utils/strings.dart';
import '/utils/storage/session_cookie.dart';
import 'flash_message.dart';

class QrReader extends StatefulWidget {
  const QrReader({
    Key? key,
  }) : super(key: key);

  @override
  State<QrReader> createState() => _QrReaderState();
}

class _QrReaderState extends State<QrReader> {
  final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
  Barcode? result;
  QRViewController? controller;

  // In order to get hot reload to work we need to pause the camera if the platform
  // is android, or resume the camera if the platform is iOS.
  @override
  void reassemble() {
    super.reassemble();
    if (Platform.isAndroid) {
      controller!.pauseCamera();
    } else if (Platform.isIOS) {
      controller!.resumeCamera();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: <Widget>[
          Expanded(
            flex: 5,
            child: QRView(
              key: qrKey,
              onQRViewCreated: _onQRViewCreated,
              formatsAllowed: const [BarcodeFormat.qrcode],
              overlay: QrScannerOverlayShape(),
            ),
          ),
        ],
      ),
    );
  }

  void _onQRViewCreated(QRViewController controller) {
    this.controller = controller;
    controller.scannedDataStream.listen((scanData) {
      addFriend(scanData)
          .then((dynamic ret) {
            if (ret) {
              // Delay exit to prevent exit mid way through rendering
              Future.delayed(Duration.zero, () {
                Navigator.of(context).pop();
              });
            }
          });
    });
  }

  @override
  void dispose() {
    controller?.dispose();
    super.dispose();
  }

  Future<bool> addFriend(Barcode scanData) async {
    Map<String, dynamic> friendJson = jsonDecode(scanData.code!);

    RSAPublicKey publicKey = CryptoUtils.rsaPublicKeyFromPem(
      String.fromCharCodes(
        base64.decode(
            friendJson['k']
        )
      )
    );

    MyProfile profile = await MyProfile.getProfile();

    var uuid = const Uuid();

    final symmetricKey1 = AesHelper.deriveKey(generateRandomString(32));
    final symmetricKey2 = AesHelper.deriveKey(generateRandomString(32));

    Friend request1 = Friend(
      id: uuid.v4(),
      userId: friendJson['i'],
      username: profile.username,
      friendId: profile.id,
      friendSymmetricKey: base64.encode(symmetricKey1),
      publicKey: profile.publicKey!,
      acceptedAt: DateTime.now(),
    );

    Friend request2 = Friend(
      id: uuid.v4(),
      userId: profile.id,
      friendId: friendJson['i'],
      username: friendJson['u'],
      friendSymmetricKey: base64.encode(symmetricKey2),
      publicKey: publicKey,
      acceptedAt: DateTime.now(),
    );

    String payload = jsonEncode([
      request1.payloadJson(),
      request2.payloadJson(),
    ]);

    var resp = await http.post(
      await MyProfile.getServerUrl('api/v1/auth/friend_request/qr_code'),
      headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
        'cookie': await getSessionCookie(),
      },
      body: payload,
    );

    if (resp.statusCode != 200) {
      showMessage(
        'Failed to add friend, please try again later',
        context
      );
      return false;
    }

    final db = await getDatabaseConnection();

    await db.insert(
        'friends',
        request1.toMap(),
        conflictAlgorithm: ConflictAlgorithm.replace,
    );

    await db.insert(
        'friends',
        request2.toMap(),
        conflictAlgorithm: ConflictAlgorithm.replace,
    );

    return true;
  }
}
