/**
 * Pair — secondary-device onboarding.
 *
 * Flow (new device, this screen):
 *   1. Generate a fresh X25519 keypair locally + random 6-digit code.
 *   2. Display { code, device x25519 pub }. User enters both on their
 *      primary device (Settings → Devices → Link new device).
 *   3. Primary device:
 *        - submits LINK_DEVICE tx to publish our pub under its master,
 *        - sends a relay envelope to our x25519 pub, encrypted with its
 *          own x25519 priv, containing { code, master_pub, master_priv,
 *          master_x25519_pub }.
 *   4. We poll /relay/inbox every few seconds; when a decryptable
 *      envelope arrives whose payload.code matches our displayed code,
 *      we treat it as the handshake, assemble a KeyFile, save it, and
 *      redirect into (app).
 *
 * Security notes:
 *   - Master Ed25519 priv travels only via this envelope, encrypted for
 *     this device's X25519 pub (which only this device holds the priv
 *     for). Exposure is limited to one successful decrypt; we DELETE
 *     the envelope from the mailbox immediately.
 *   - The 6-digit code defends against a confused primary device paired
 *     with a different victim, or a race with an attacker who guesses
 *     our X25519 pub. Envelope without matching code → ignored.
 *   - Envelope is short-lived in the mailbox: we DELETE on first decrypt
 *     and the relay node has its own TTL.
 */
import React, { useCallback, useEffect, useRef, useState } from 'react';
import {
  View, Text, Pressable, ActivityIndicator, ScrollView,
} from 'react-native';
import { router } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
import * as Clipboard from 'expo-clipboard';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import nacl from 'tweetnacl';

import { useStore } from '@/lib/store';
import { bytesToHex, decryptMessage } from '@/lib/crypto';
import { fetchInbox } from '@/lib/api';
import { saveKeyFile, markDeviceRegistered } from '@/lib/storage';
import { safeBack } from '@/lib/utils';
import type { KeyFile } from '@/lib/types';

// Protocol constant — bump if payload shape changes.
const PAIR_ENVELOPE_VERSION = 1;

interface PairEnvelopePayload {
  v:             number;
  type:          'pair-handshake';
  code:          string;
  master_pub:    string;   // Ed25519 pub, hex
  master_priv:   string;   // Ed25519 priv, hex
  master_x25519_pub: string;  // primary device's x25519 pub, hex (for nothing special — just FYI)
}

function randomCode(): string {
  // Six decimal digits. Entropy ~20 bits. Good enough for a one-shot
  // rendezvous code gated by an out-of-band delivery channel (envelope
  // targeted at a freshly-generated X25519 pub that only this device
  // holds the priv for).
  const n = Math.floor(Math.random() * 1_000_000);
  return n.toString().padStart(6, '0');
}

export default function PairScreen() {
  const insets       = useSafeAreaInsets();
  const setKeyFile   = useStore(s => s.setKeyFile);

  // One-shot keypair + code for this pairing session. Regenerate only on
  // manual Retry (unmount+remount).
  const session = useRef(genSession()).current;

  const [status, setStatus] = useState<'waiting' | 'success' | 'error'>('waiting');
  const [err,    setErr]    = useState<string | null>(null);

  const copyCode = useCallback(async () => {
    await Clipboard.setStringAsync(session.code);
  }, [session.code]);

  const copyPub = useCallback(async () => {
    await Clipboard.setStringAsync(session.x25519Pub);
  }, [session.x25519Pub]);

  // Poll mailbox until a matching handshake envelope arrives, or user
  // backs out. Interval 2.5s — conservative on battery, fine for a
  // flow the user is staring at for a minute.
  useEffect(() => {
    let cancelled = false;
    let timer: ReturnType<typeof setTimeout> | null = null;

    const tick = async () => {
      if (cancelled) return;
      try {
        const envs = await fetchInbox(session.x25519Pub);
        for (const env of envs) {
          // Decrypt each envelope with our session priv. We don't know
          // the primary's x25519 pub up front — it's inside the envelope
          // metadata. decryptMessage needs both pubs, so we pass the
          // envelope's sender_pub directly.
          const plain = decryptMessage(
            env.ciphertext, env.nonce, env.sender_pub, session.x25519Priv,
          );
          if (!plain) continue;
          let payload: PairEnvelopePayload;
          try {
            payload = JSON.parse(plain);
          } catch { continue; }
          if (
            payload.v !== PAIR_ENVELOPE_VERSION ||
            payload.type !== 'pair-handshake' ||
            payload.code !== session.code ||
            !payload.master_pub || !payload.master_priv
          ) continue;

          // Success — materialise a KeyFile and redirect.
          const kf: KeyFile = {
            pub_key:     payload.master_pub,
            priv_key:    payload.master_priv,
            x25519_pub:  session.x25519Pub,
            x25519_priv: session.x25519Priv,
          };
          await saveKeyFile(kf);
          // The root layout auto-links us on first boot if needed, but
          // the primary device already submitted LINK_DEVICE for our
          // pub as part of the pairing, so the registry is already
          // correct. Mark ourselves registered so the revoke-detection
          // branch doesn't spuriously wipe on next launch.
          await markDeviceRegistered();
          setKeyFile(kf);

          // Envelope stays in mailbox until relay TTL eviction; the
          // single-shot handshake is idempotent (saveKeyFile overwrites)
          // and our session pub won't be polled again after redirect.
          setStatus('success');
          setTimeout(() => {
            if (!cancelled) router.replace('/(app)/chats' as never);
          }, 600);
          return;
        }
      } catch {
        /* transient — retry */
      }
      if (!cancelled) timer = setTimeout(tick, 2_500);
    };

    tick();
    return () => {
      cancelled = true;
      if (timer) clearTimeout(timer);
    };
  }, [session, setKeyFile]);

  return (
    <View style={{
      flex: 1, backgroundColor: '#000000',
      paddingTop: insets.top + 12,
      paddingBottom: Math.max(insets.bottom, 20),
    }}>
      <ScrollView
        contentContainerStyle={{ padding: 24 }}
        showsVerticalScrollIndicator={false}
      >
        {/* Header */}
        <Pressable
          onPress={() => safeBack('/')}
          hitSlop={8}
          style={{ alignSelf: 'flex-start', marginBottom: 20 }}
        >
          <Ionicons name="chevron-back" size={28} color="#ffffff" />
        </Pressable>

        <View style={{ alignItems: 'center', marginBottom: 28 }}>
          <View style={{
            width: 80, height: 80, borderRadius: 22,
            backgroundColor: '#0a0a0a',
            borderWidth: 1, borderColor: '#1f1f1f',
            alignItems: 'center', justifyContent: 'center',
          }}>
            <Ionicons name="link" size={40} color="#1d9bf0" />
          </View>
          <Text style={{
            color: '#ffffff', fontSize: 22, fontWeight: '800',
            marginTop: 14, textAlign: 'center',
          }}>
            Pair with your other device
          </Text>
          <Text style={{
            color: '#8b8b8b', fontSize: 13, lineHeight: 19,
            marginTop: 8, textAlign: 'center', maxWidth: 300,
          }}>
            On a device where you're already signed in,
            open Settings → Devices → Link new device,
            and enter these values.
          </Text>
        </View>

        {/* Code */}
        <View style={{
          borderRadius: 14,
          backgroundColor: '#0a0a0a',
          borderWidth: 1, borderColor: '#1f1f1f',
          paddingVertical: 18, paddingHorizontal: 16,
          marginBottom: 14, alignItems: 'center',
        }}>
          <Text style={{
            color: '#5a5a5a', fontSize: 11, fontWeight: '700',
            textTransform: 'uppercase', letterSpacing: 1.2,
          }}>
            1. Code
          </Text>
          <Text style={{
            color: '#ffffff', fontSize: 38, fontWeight: '800',
            letterSpacing: 6, marginTop: 4, fontFamily: 'monospace',
          }}>
            {session.code.slice(0, 3)} {session.code.slice(3)}
          </Text>
          <Pressable onPress={copyCode} hitSlop={6} style={{ marginTop: 6 }}>
            <Text style={{ color: '#1d9bf0', fontSize: 12, fontWeight: '600' }}>
              Copy code
            </Text>
          </Pressable>
        </View>

        {/* Device key */}
        <View style={{
          borderRadius: 14,
          backgroundColor: '#0a0a0a',
          borderWidth: 1, borderColor: '#1f1f1f',
          paddingVertical: 16, paddingHorizontal: 16,
          marginBottom: 20,
        }}>
          <Text style={{
            color: '#5a5a5a', fontSize: 11, fontWeight: '700',
            textTransform: 'uppercase', letterSpacing: 1.2,
          }}>
            2. Device key
          </Text>
          <Text
            selectable
            style={{
              color: '#ffffff', fontSize: 12, fontFamily: 'monospace',
              marginTop: 6, lineHeight: 17,
            }}
          >
            {session.x25519Pub}
          </Text>
          <Pressable onPress={copyPub} hitSlop={6} style={{ marginTop: 8 }}>
            <Text style={{ color: '#1d9bf0', fontSize: 12, fontWeight: '600' }}>
              Copy key
            </Text>
          </Pressable>
        </View>

        {/* Status */}
        <View style={{ alignItems: 'center', minHeight: 56 }}>
          {status === 'waiting' && (
            <View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
              <ActivityIndicator size="small" color="#1d9bf0" />
              <Text style={{ color: '#8b8b8b', fontSize: 13 }}>
                Waiting for your other device…
              </Text>
            </View>
          )}
          {status === 'success' && (
            <View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
              <Ionicons name="checkmark-circle" size={20} color="#3ba55d" />
              <Text style={{ color: '#3ba55d', fontSize: 13, fontWeight: '700' }}>
                Paired. Opening your chats…
              </Text>
            </View>
          )}
          {status === 'error' && (
            <Text style={{ color: '#f4212e', fontSize: 13, textAlign: 'center' }}>
              {err ?? 'Something went wrong. Please retry.'}
            </Text>
          )}
        </View>
      </ScrollView>
    </View>
  );
}

// ─── session helper ──────────────────────────────────────────────────────

interface PairSession {
  x25519Pub:  string;  // hex
  x25519Priv: string;  // hex
  code:       string;
}

function genSession(): PairSession {
  const kp = nacl.box.keyPair();
  return {
    x25519Pub:  bytesToHex(kp.publicKey),
    x25519Priv: bytesToHex(kp.secretKey),
    code:       randomCode(),
  };
}
