// Detail filmu — plakat, opis, cast, chaptery, mirrory.
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Image } from 'expo-image';
import React from 'react';
import {
  ActivityIndicator,
  Alert,
  Linking,
  Pressable,
  ScrollView,
  StyleSheet,
  Text,
  View,
} from 'react-native';
import { useClient } from '../ClientContext';
import type { RootStackParamList } from '../navigation';
import { theme } from '../theme';
import type { PlaybackSource, StreamLink } from '../types';
import { resolveVoePage } from '../lib/voeResolver';
import { PlaybackQualityModal } from './PlaybackQualityModal';

export function MovieDetailScreen() {
  const client = useClient();
  const queryClient = useQueryClient();
  const route = useRoute<RouteProp<RootStackParamList, 'MovieDetail'>>();
  const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList, 'MovieDetail'>>();
  const { id } = route.params;

  const { data, isLoading, error } = useQuery({
    queryKey: ['movie', id],
    queryFn: () => client.getMovie(id),
  });

  const isFav = data?.is_favorite ?? false;
  const favMutation = useMutation({
    mutationFn: () =>
      isFav ? client.removeFavoriteMovie(id) : client.addFavoriteMovie(id),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['movie', id] });
      queryClient.invalidateQueries({ queryKey: ['favorites-movies'] });
    },
  });

  React.useLayoutEffect(() => {
    navigation.setOptions({
      title: data?.title ?? 'Movie',
      headerRight: () => (
        <Pressable
          onPress={() => favMutation.mutate()}
          hitSlop={12}
          disabled={favMutation.isPending}
        >
          <Text style={{ color: isFav ? theme.accent : theme.muted, fontSize: 22 }}>
            {isFav ? '★' : '☆'}
          </Text>
        </Pressable>
      ),
    });
  }, [navigation, data?.title, isFav, favMutation]);

  if (isLoading) {
    return (
      <View style={styles.center}>
        <ActivityIndicator color={theme.fg} />
      </View>
    );
  }
  if (error instanceof Error) {
    return (
      <View style={styles.center}>
        <Text style={styles.errorText}>{error.message}</Text>
      </View>
    );
  }
  if (!data) return null;

  const dur = data.duration_sec
    ? `${Math.floor(data.duration_sec / 3600) > 0 ? `${Math.floor(data.duration_sec / 3600)}h ` : ''}${Math.floor((data.duration_sec % 3600) / 60)}min`
    : null;

  return (
    <ScrollView style={styles.container} contentContainerStyle={{ paddingBottom: 32 }}>
      <View style={styles.heroRow}>
        {data.poster_url ? (
          <Image source={{ uri: data.poster_url }} style={styles.poster} contentFit="cover" />
        ) : (
          <View style={[styles.poster, styles.posterPlaceholder]} />
        )}
        <View style={styles.heroMeta}>
          <Text style={styles.title}>{data.title}</Text>
          <Text style={styles.subtitle}>
            {data.release_year ?? '—'}
            {data.studio?.name ? ` · ${data.studio.name}` : ''}
            {dur ? ` · ${dur}` : ''}
          </Text>
          {data.director ? <Text style={styles.subtitle}>dir. {data.director}</Text> : null}
          {data.country ? <Text style={styles.subtitle}>{data.country}</Text> : null}
          {data.rating != null ? (
            <Text style={styles.rating}>★ {data.rating.toFixed(1)}/10</Text>
          ) : null}
        </View>
      </View>

      {data.description ? (
        <View style={styles.section}>
          <Text style={styles.body}>{data.description}</Text>
        </View>
      ) : null}

      {data.performers.length > 0 ? (
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>Cast</Text>
          <View style={styles.chipRow}>
            {data.performers.map((p) => (
              <Pressable
                key={p.id}
                style={styles.chip}
                onPress={() =>
                  navigation.navigate('PerformerScenes', { id: p.id, name: p.canonical_name })
                }
              >
                <Text style={styles.chipText}>{p.canonical_name}</Text>
              </Pressable>
            ))}
          </View>
        </View>
      ) : null}

      {data.tags.length > 0 ? (
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>Genres</Text>
          <View style={styles.chipRow}>
            {data.tags.map((t) => (
              <View key={t.id} style={styles.tagChip}>
                <Text style={styles.chipText}>{t.name}</Text>
              </View>
            ))}
          </View>
        </View>
      ) : null}

      {data.chapters.length > 0 ? (
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>Chapters ({data.chapters.length})</Text>
          {data.chapters.map((c) => (
            <View key={c.chapter_index} style={styles.chapterRow}>
              <Text style={styles.chapterIndex}>{c.chapter_index + 1}.</Text>
              <Text style={styles.chapterTitle}>{c.title ?? `Part ${c.chapter_index + 1}`}</Text>
              {c.start_sec != null ? (
                <Text style={styles.chapterTime}>{formatTime(c.start_sec)}</Text>
              ) : null}
            </View>
          ))}
        </View>
      ) : null}

      {data.playback_sources.length > 0 ? (
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>
            Watch on ({data.playback_sources.length})
          </Text>
          <View style={styles.chipRow}>
            {data.playback_sources.map((p) => (
              <WatchChip key={p.id} pb={p} movieId={data.id} title={data.title} />
            ))}
          </View>
          <Text style={styles.chipHint}>long-press: open in browser / mark as broken</Text>
        </View>
      ) : null}
    </ScrollView>
  );
}

function WatchChip({
  pb,
  movieId,
  title,
}: {
  pb: PlaybackSource;
  movieId: string;
  title: string;
}) {
  const client = useClient();
  const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList, 'MovieDetail'>>();

  const queryClient = useQueryClient();
  // Multipart picker — paradisehill movie z N częściami. Modal zamiast Alert.alert,
  // bo Androidowy AlertDialog renderuje max 3 buttony (bug-report 2ebd0690: "35 parts,
  // 3 w popup"). PlaybackQualityModal jest przewijalny i pokazuje wszystkie.
  const [partsPicker, setPartsPicker] = React.useState<StreamLink[] | null>(null);

  const playPart = React.useCallback(
    (p: StreamLink) => {
      setPartsPicker(null);
      const pDirect = p.direct_url;
      const pIsDirect = !!pDirect && pDirect !== p.stream_url;
      navigation.navigate('Player', {
        url: pDirect || p.stream_url || p.embed_url || pb.page_url,
        origin: pb.origin,
        sceneId: movieId,
        playbackId: pb.id,
        entityKind: 'movie',
        durationSec: pb.duration_sec ?? null,
        title: `${title} — ${(p.raw as any)?.part_label ?? p.quality}`,
        mode: (p.direct_url || p.stream_url) ? 'video' : 'webview',
        headers: pIsDirect && p.headers ? p.headers : undefined,
        fallbackProxyUrl: pIsDirect ? p.stream_url || undefined : undefined,
        fallbackEmbedUrl: p.embed_url || pb.embed_url || pb.page_url,
      });
    },
    [navigation, pb, movieId, title],
  );

  // VOE: token CDN bound do /16 IP które pobrało embed (z VPS = i=46.62 → telefon 403
  // → pełny proxy → wideo przez Hetzner, 131 hitów/48h). Telefon sam pobiera embed →
  // token bound do jego /16 → gra direct, zero VPS. [] → spadnij na backend resolve.
  const [voeResolving, setVoeResolving] = React.useState(false);
  const playVoe = React.useCallback(async () => {
    setVoeResolving(true);
    try {
      const links = await resolveVoePage(pb.page_url || pb.embed_url || '');
      const best = links[0];
      if (best && (best.direct_url || best.stream_url)) {
        navigation.navigate('Player', {
          url: best.direct_url || best.stream_url!,
          origin: pb.origin,
          sceneId: movieId,
          playbackId: pb.id,
          entityKind: 'movie',
          durationSec: pb.duration_sec ?? null,
          title,
          headers: best.headers ?? undefined,
          // Zero VPS: bez fallbackProxyUrl (proxy = IP-bound do VPS, bez sensu). Na błąd
          // → WebView z embed page (voe player sam pobierze m3u8 w swoim kontekście).
          fallbackEmbedUrl: pb.page_url || pb.embed_url || undefined,
        });
        return;
      }
    } catch {
      // ignore → backend fallback
    } finally {
      setVoeResolving(false);
    }
    resolveMutation.mutate();
  }, [pb, movieId, title, navigation]);

  const resolveMutation = useMutation({
    mutationFn: () => client.resolveMoviePlayback(movieId, pb.id),
    onSuccess: (res) => {
      // best.stream_url to backend proxy URL gdy direct video się udało wyciągnąć,
      // lub embed_url gdy hoster nieudany — Player handler-uje obie ścieżki.
      // _absolutizeProxyUrls w GoonClient już prefixuje /proxy/... baseUrl-em.

      // Multipart: paradisehill movies mają `videoList` z N MP4 parts. Backend
      // zwraca każdy jako StreamLink z `quality = "Part N"` + `raw.part_label`.
      // Bez part-picker mobile używałby tylko best (Part 1) — user nie miałby
      // dostępu do reszty (bug-reports `c5693926`/`418270e4` 2026-05-21).
      const links = res.links ?? [];
      const parts = links.filter((l) => l.raw && typeof l.raw === 'object' && (l.raw as any).part_label);
      if (parts.length > 1) {
        // Scrollowalny modal (nie Alert.alert — Android capuje do 3 buttonów).
        setPartsPicker(parts);
        return;
      }

      // Preferuj direct CDN URL (0 VPS bandwidth) → fallback proxy gdy direct fails.
      // seekplayer-engine (#hash family, ~322k źródeł) zwraca master.m3u8 na raw-IP CDN
      // z VALID ZeroSSL IP-SAN cert + time-bound token — zweryfikowane na emulatorze
      // (ExoPlayer gra direct, PLAYING, zero VPS proxy). Wcześniej movie path szedł
      // ZAWSZE przez proxy (używał stream_url jako primary). Mirror SceneDetailScreen.
      const bestDirect = res.best?.direct_url;
      const isDirect = !!bestDirect && bestDirect !== res.best?.stream_url;
      const target = bestDirect || res.best?.stream_url || res.best?.embed_url || pb.page_url;
      const fallbackEmbed = res.best?.embed_url || pb.embed_url || pb.page_url;
      navigation.navigate('Player', {
        url: target,
        origin: pb.origin,
        // sceneId pozostaje nazwą param-u (legacy z kiedy Player obsługiwał tylko sceny),
        // ale dla entityKind='movie' Player rzutuje to do /movies/{id}/progress.
        // Bug-report b207ff17 2026-05-26 ("oznaczenie obejrzanych filmów") — backend
        // dostał movie_play_progress 2026-05-28.
        sceneId: movieId,
        playbackId: pb.id,
        entityKind: 'movie',
        durationSec: pb.duration_sec ?? null,
        title,
        mode: (res.best?.direct_url || res.best?.stream_url) ? 'video' : 'webview',
        headers: isDirect && res.best?.headers ? res.best.headers : undefined,
        fallbackProxyUrl: isDirect ? res.best?.stream_url || undefined : undefined,
        fallbackEmbedUrl: fallbackEmbed,
      });
    },
    onError: (e: any) => {
      Alert.alert('Resolve failed', e?.message ?? 'unknown error');
    },
  });

  const onLongPress = () => {
    Alert.alert(
      pb.origin,
      'What do you want to do with this link?',
      [
        {
          text: 'Open in browser (diagnostics)',
          onPress: async () => {
            try {
              const url = pb.page_url || pb.embed_url;
              if (url) {
                await Linking.openURL(url);
              } else {
                Alert.alert('No URL', 'This playback has no page_url to open.');
              }
            } catch (e) {
              Alert.alert('Could not open', e instanceof Error ? e.message : String(e));
            }
          },
        },
        {
          text: 'Mark as invalid',
          style: 'destructive',
          onPress: async () => {
            try {
              await client.markMoviePlaybackDead(movieId, pb.id);
              queryClient.invalidateQueries({ queryKey: ['movie', movieId] });
            } catch (e) {
              Alert.alert('Failed', e instanceof Error ? e.message : String(e));
            }
          },
        },
        { text: 'Cancel', style: 'cancel' },
      ],
    );
  };

  return (
    <>
      <Pressable
        style={[styles.watchChip, (resolveMutation.isPending || voeResolving) && styles.watchChipLoading]}
        onPress={() => (pb.origin.endsWith(':voe') ? playVoe() : resolveMutation.mutate())}
        onLongPress={onLongPress}
        delayLongPress={500}
        disabled={resolveMutation.isPending || voeResolving}
      >
        <Text style={styles.watchChipText}>▶ {pb.origin}</Text>
      </Pressable>
      <PlaybackQualityModal
        visible={!!partsPicker}
        links={partsPicker ?? []}
        title="Select part"
        preserveOrder
        onSelect={playPart}
        onCancel={() => setPartsPicker(null)}
      />
    </>
  );
}

function formatTime(s: number): string {
  const total = Math.floor(s);
  const m = Math.floor(total / 60);
  const sec = total % 60;
  return `${m}:${String(sec).padStart(2, '0')}`;
}

const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: theme.bg },
  center: { flex: 1, alignItems: 'center', justifyContent: 'center' },
  errorText: { color: theme.bad },

  heroRow: { flexDirection: 'row', padding: 12, gap: 12 },
  poster: { width: 120, aspectRatio: 2 / 3, borderRadius: 8, backgroundColor: theme.card },
  posterPlaceholder: { width: 120 },
  heroMeta: { flex: 1, gap: 4 },
  title: { color: theme.fg, fontSize: 18, fontWeight: '700' },
  subtitle: { color: theme.muted, fontSize: 13 },
  rating: { color: theme.accent, fontSize: 14, fontWeight: '700', marginTop: 4 },

  section: { paddingHorizontal: 14, paddingVertical: 10 },
  sectionTitle: { color: theme.fg, fontSize: 14, fontWeight: '700', marginBottom: 8 },
  body: { color: theme.fg, fontSize: 13, lineHeight: 19 },

  chipRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 6 },
  chip: {
    backgroundColor: theme.card,
    borderColor: theme.border,
    borderWidth: 1,
    borderRadius: 14,
    paddingHorizontal: 10,
    paddingVertical: 5,
  },
  tagChip: {
    backgroundColor: 'rgba(255,255,255,0.05)',
    borderRadius: 12,
    paddingHorizontal: 9,
    paddingVertical: 4,
  },
  chipText: { color: theme.fg, fontSize: 12 },
  watchChip: {
    backgroundColor: theme.accent,
    borderRadius: 14,
    paddingHorizontal: 12,
    paddingVertical: 7,
  },
  watchChipLoading: { opacity: 0.5 },
  watchChipText: { color: theme.fg, fontSize: 12, fontWeight: '700' },
  chipHint: { color: theme.muted, fontSize: 11, marginTop: 8, fontStyle: 'italic' },

  chapterRow: { flexDirection: 'row', alignItems: 'center', paddingVertical: 6, gap: 8 },
  chapterIndex: { color: theme.muted, fontSize: 13, width: 28 },
  chapterTitle: { color: theme.fg, fontSize: 14, flex: 1 },
  chapterTime: { color: theme.muted, fontSize: 12 },
});
