import { useEffect, useState, useRef, useCallback, useMemo } from 'react';
import './App.css';
import { getUser } from './auth/authService';
import { getStoredPasskeyUser } from './auth/passkeyService';
import { fromOidcUser, type AuthUser } from './auth/types';
import AlertError from './components/AlertError';
import LoginModal from './components/LoginModal';
import AuthCallback from './components/AuthCallback';
import { Map } from './components/Map';
import { FilterPanel, type ParameterValues, DEFAULT_FILTER_VALUES, Metric } from './components/FilterPanel';
import { VisualizationCard } from './components/VisualizationCard';
import { Header } from './components/Header';
import { StatsBar, type ViewMode } from './components/StatsBar';
import { ListView } from './components/ListView';
import { StreamingProgressBar } from './components/StreamingProgressBar';
import { Sheet, SheetContent, SheetTrigger } from './components/ui/sheet';
import { Button } from './components/ui/button';
import { Filter, Heart } from 'lucide-react';
import type { GeoJSONFeatureCollection, PropertyProperties, PropertyFeature, POI, POITravelFilter } from '@/types';
import { refreshListings, streamListingGeoJSON, fetchUserPOIs, type StreamingProgress } from '@/services';
import { setOnUnauthorized } from '@/services/apiClient';
import { clearPasskeyUser } from './auth/passkeyService';
import { poiMetricPropertyName, injectPoiMetricProperty } from '@/utils/poiUtils';
import { useTaskProgress } from '@/hooks/useTaskProgress';
import { useDecisions } from '@/hooks/useDecisions';
import { useIsMobile } from '@/hooks/use-mobile';
import { MobileBottomSheet } from './components/MobileBottomSheet';
import { SwipeReviewMode } from './components/SwipeReviewMode';
import { FavoritesView } from './components/FavoritesView';
import { ListingDetailSheet } from './components/ListingDetailSheet';

function isTerminalStatus(status: string): boolean {
  return status === 'SUCCESS' || status === 'FAILURE' || status === 'REVOKED';
}

function App() {
  const [listingData, setListingData] = useState<GeoJSONFeatureCollection | null>(null);
  const [user, setUser] = useState<AuthUser | null>(null);
  const [queryParameters, setQueryParameters] = useState<ParameterValues | null>(null);
  const [submitError, setSubmitError] = useState<string | null>(null);
  const [alertDialogIsOpen, setAlertDialogIsOpen] = useState(false);
  const [isLoading, setIsLoading] = useState(false);
  const [viewMode, setViewMode] = useState<ViewMode>('map');
  const [mobileFilterOpen, setMobileFilterOpen] = useState(false);
  const [highlightedProperty, setHighlightedProperty] = useState<string | null>(null);
  const [streamingProgress, setStreamingProgress] = useState<StreamingProgress | null>(null);
  const [userPOIs, setUserPOIs] = useState<POI[]>([]);
  const [poiPickerActive, setPoiPickerActive] = useState(false);
  const [pickedPoiLocation, setPickedPoiLocation] = useState<{ lat: number; lng: number } | null>(null);
  const [poiMetricSelection, setPoiMetricSelection] = useState<{
    poiId: number;
    poiName: string;
    travelMode: 'WALK' | 'BICYCLE' | 'TRANSIT';
  } | null>(null);
  const [poiTravelFilters, setPoiTravelFilters] = useState<Record<number, POITravelFilter>>({});
  const [currentMetric, setCurrentMetric] = useState<Metric>(DEFAULT_FILTER_VALUES.metric);
  const isMobile = useIsMobile();
  const [, setActiveCardFeature] = useState<PropertyFeature | null>(null);
  const [showReviewMode, setShowReviewMode] = useState(false);
  const [selectedListingId, setSelectedListingId] = useState<number | null>(null);

  // Decision state (like/dislike)
  const { decide, clear, getDecision, likedCount, isLoaded: isDecisionsLoaded } = useDecisions(user);

  // Explicit task ID set by fetch-data action (to track as "active")
  const [explicitTaskId, setExplicitTaskId] = useState<string | null>(null);

  // Unified task progress: WS primary, polling fallback
  const { tasks, isConnected, subscribe, cancelTask, clearAllTasks } = useTaskProgress(user);

  // Derive activeTaskId: explicit ID if set, else most recent non-terminal task
  const activeTaskId = useMemo(() => {
    if (explicitTaskId && tasks[explicitTaskId]) return explicitTaskId;
    // Fall back to any non-terminal task
    const nonTerminal = Object.entries(tasks).filter(
      ([, t]) => !isTerminalStatus(t.status),
    );
    if (nonTerminal.length > 0) return nonTerminal[0][0];
    // Fall back to explicit even if terminal (to show final status)
    if (explicitTaskId && tasks[explicitTaskId]) return explicitTaskId;
    // Show most recent task if any
    const allIds = Object.keys(tasks);
    return allIds.length > 0 ? allIds[allIds.length - 1] : null;
  }, [explicitTaskId, tasks]);

  // Ref to track accumulated features during streaming
  const accumulatedFeaturesRef = useRef<PropertyFeature[]>([]);
  // Ref to track if initial load has been triggered
  const initialLoadTriggeredRef = useRef(false);
  // Ref to abort in-flight streaming requests
  const abortControllerRef = useRef<AbortController | null>(null);

  // Check if this is the callback route - render dedicated component
  if (window.location.pathname === '/callback') {
    return <AuthCallback />;
  }

  useEffect(() => {
    // Check passkey user first, then fall back to OIDC
    const passkeyUser = getStoredPasskeyUser();
    if (passkeyUser) {
      setUser(passkeyUser);
    } else {
      getUser().then((oidcUser) => {
        if (oidcUser) {
          setUser(fromOidcUser(oidcUser));
        }
      });
    }
  }, []);

  useEffect(() => {
    setOnUnauthorized(() => {
      clearPasskeyUser();
      setUser(null);
    });
  }, []);

  const handlePasskeyLogin = (passkeyUser: AuthUser) => {
    setUser(passkeyUser);
  };

  // Load user's POIs
  useEffect(() => {
    if (!user) return;
    fetchUserPOIs(user).then(setUserPOIs).catch(() => {});
  }, [user]);

  // Load listings function - used by both auto-load and manual submit
  const loadListings = useCallback(async (parameters: ParameterValues) => {
    if (!user) return;

    // Abort any in-flight streaming request
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
    }
    const controller = new AbortController();
    abortControllerRef.current = controller;

    setQueryParameters(parameters);
    setMobileFilterOpen(false);
    setIsLoading(true);
    accumulatedFeaturesRef.current = [];
    setStreamingProgress({ count: 0 });
    setListingData(null);

    // Dedup safety net: track seen URLs to prevent duplicate features
    const seenUrls = new Set<string>();

    let updateScheduled = false;

    const flushUpdate = () => {
      updateScheduled = false;
      setListingData({
        type: 'FeatureCollection',
        features: [...accumulatedFeaturesRef.current]
      });
    };

    const scheduleUpdate = () => {
      if (!updateScheduled) {
        updateScheduled = true;
        requestAnimationFrame(flushUpdate);
      }
    };

    try {
      for await (const batch of streamListingGeoJSON(user, parameters, (progress) => {
        setStreamingProgress(progress);
      }, { includePoiDistances: userPOIs.length > 0, signal: controller.signal })) {
        // Deduplicate features by URL
        const uniqueBatch = batch.filter((feature) => {
          const url = feature.properties?.url;
          if (!url || seenUrls.has(url)) return false;
          seenUrls.add(url);
          return true;
        });
        if (uniqueBatch.length > 0) {
          accumulatedFeaturesRef.current.push(...uniqueBatch);
          scheduleUpdate();
        }
      }
      // Final flush to ensure all data is rendered
      flushUpdate();
    } catch (error) {
      // Silently ignore AbortError — it means we intentionally cancelled
      if (error instanceof DOMException && error.name === 'AbortError') {
        return;
      }
      if (error instanceof Error) {
        setSubmitError(error.message);
      } else {
        setSubmitError(String(error));
      }
      setAlertDialogIsOpen(true);
    } finally {
      // Only clear loading state if this controller is still the current one
      if (abortControllerRef.current === controller) {
        setIsLoading(false);
        setStreamingProgress(null);
      }
    }
  }, [user, userPOIs]);

  // Compute processed listing data: inject synthetic POI metric property & apply max travel filter
  const processedListingData = useMemo(() => {
    if (!listingData) return null;

    let features = listingData.features;

    // Inject synthetic flat property for the selected POI metric
    if (poiMetricSelection) {
      features = injectPoiMetricProperty(
        features,
        poiMetricSelection.poiId,
        poiMetricSelection.travelMode,
      );
    }

    // Filter by per-POI max travel time (AND logic: listing must satisfy all active filters)
    const activeFilters = Object.entries(poiTravelFilters)
      .filter(([, f]) => f.maxMinutes !== undefined)
      .map(([id, f]) => ({ poiId: Number(id), travelMode: f.travelMode, maxMinutes: f.maxMinutes! }));

    if (activeFilters.length > 0) {
      features = features.filter((f) => {
        const distances = f.properties.poi_distances;
        if (!distances) return false;
        return activeFilters.every((filter) => {
          const dist = distances.find(
            (d) => d.poi_id === filter.poiId && d.travel_mode === filter.travelMode,
          );
          return dist !== undefined && dist.duration_seconds <= filter.maxMinutes * 60;
        });
      });
    }

    // Filter out disliked listings (client-side for instant feedback)
    if (isDecisionsLoaded) {
      features = features.filter((f) => {
        const parts = f.properties.url.split('/');
        const id = parseInt(parts[parts.length - 1], 10);
        const type = f.properties.listing_type === 'BUY' ? 'BUY' : 'RENT';
        return getDecision(id, type) !== 'disliked';
      });
    }

    return { ...listingData, features };
  }, [listingData, poiMetricSelection, poiTravelFilters, isDecisionsLoaded, getDecision]);

  // Compute the effective metric string for the heatmap
  const effectiveMetric = useMemo(() => {
    if (queryParameters?.metric === Metric.poi_travel && poiMetricSelection) {
      return poiMetricPropertyName(poiMetricSelection.poiId, poiMetricSelection.travelMode);
    }
    return queryParameters?.metric;
  }, [queryParameters?.metric, poiMetricSelection]);

  // Auto-load data with default filters when user is authenticated
  useEffect(() => {
    if (!user || initialLoadTriggeredRef.current) {
      return;
    }
    initialLoadTriggeredRef.current = true;

    const defaultParams: ParameterValues = {
      ...DEFAULT_FILTER_VALUES,
      available_from: new Date(),
    };

    loadListings(defaultParams);
  }, [user, loadListings]);

  const handleTaskCompleted = useCallback(() => {
    if (queryParameters) {
      loadListings(queryParameters);
    }
  }, [queryParameters, loadListings]);

  const handleTaskCancelled = useCallback(() => {
    setExplicitTaskId(null);
  }, []);

  const handleActiveListingChange = useCallback((feature: PropertyFeature | null) => {
    setActiveCardFeature(feature);
  }, []);

  if (!user) {
    return <LoginModal isOpen={user === null} onPasskeyLogin={handlePasskeyLogin} />;
  }

  const onSubmit = async (action: 'fetch-data' | 'visualize', parameters: ParameterValues) => {
    if (action === 'visualize') {
      loadListings(parameters);
    } else if (action === 'fetch-data') {
      setQueryParameters(parameters);
      setMobileFilterOpen(false);
      setIsLoading(true);
      try {
        const data = await refreshListings(user!, parameters);
        setExplicitTaskId(data.task_id);
        if (data.task_id) subscribe(data.task_id);
      } catch (error) {
        if (error instanceof Error) {
          setSubmitError(error.message);
        } else {
          setSubmitError(String(error));
        }
        setAlertDialogIsOpen(true);
      } finally {
        setIsLoading(false);
      }
    }
  };

  const handleMetricChange = (metric: Metric) => {
    setCurrentMetric(metric);
    setQueryParameters(prev => prev ? { ...prev, metric } : null);
  };

  const handlePropertyClick = (property: PropertyProperties, _coordinates: [number, number]) => {
    setHighlightedProperty(property.url);
    // Optionally: pan map to coordinates
  };

  const renderMainContent = () => {
    if (!processedListingData) {
      return (
        <div className="flex-1 flex items-center justify-center bg-muted/20">
          <div className="text-center p-8 max-w-md">
            {isLoading ? (
              <>
                <div className="text-6xl mb-4 animate-pulse">🏠</div>
                <h2 className="text-xl font-semibold mb-2">Loading Properties...</h2>
                <p className="text-muted-foreground mb-4">
                  Fetching listings with default filters. You can adjust filters on the left.
                </p>
              </>
            ) : (
              <>
                <div className="text-6xl mb-4">🏠</div>
                <h2 className="text-xl font-semibold mb-2">Welcome to Property Explorer</h2>
                <p className="text-muted-foreground mb-4">
                  Use the filters on the left to find properties. Apply filters to visualize existing data or refresh to fetch new listings.
                </p>
              </>
            )}
          </div>
        </div>
      );
    }

    if (processedListingData.features.length === 0) {
      return (
        <div className="flex-1 flex items-center justify-center">
          <div className="text-center p-8">
            <div className="text-6xl mb-4">🔍</div>
            <h2 className="text-xl font-semibold mb-2">No listings found</h2>
            <p className="text-muted-foreground">
              Try adjusting the filters or run a data refresh to fetch new listings.
            </p>
          </div>
        </div>
      );
    }

    if (viewMode === 'saved') {
      return (
        <FavoritesView
          listingData={listingData!}
          getDecision={getDecision}
          onSelectListing={(id) => setSelectedListingId(id)}
          onRemoveFavorite={(id, type) => clear(id, type)}
        />
      );
    }

    return (
      <>
        {/* Map View */}
        {(viewMode === 'map' || viewMode === 'split') && (
          <div className={`relative ${viewMode === 'split' ? 'w-1/2' : 'flex-1'}`} style={{ minHeight: 0 }}>
            <Map
              listingData={processedListingData}
              queryParameters={queryParameters}
              effectiveMetric={effectiveMetric}
              onPropertyClick={handlePropertyClick}
              pois={userPOIs}
              isPickingPOI={poiPickerActive}
              onPoiLocationPick={handlePoiLocationPick}
              onCancelPoiPicking={handleCancelPoiPicking}
            />
          </div>
        )}

        {/* List View */}
        {(viewMode === 'list' || viewMode === 'split') && (
          <div className={`${viewMode === 'split' ? 'w-1/2 border-l' : 'flex-1'}`}>
            <ListView
              listingData={processedListingData}
              onPropertyClick={handlePropertyClick}
              highlightedPropertyUrl={highlightedProperty}
              poiMetricSelection={poiMetricSelection}
              onSelectListing={(id) => setSelectedListingId(id)}
              onSwipeRight={(id) => decide(id, 'liked', (queryParameters?.listing_type || 'RENT') as 'RENT' | 'BUY')}
              onSwipeLeft={(id) => decide(id, 'disliked', (queryParameters?.listing_type || 'RENT') as 'RENT' | 'BUY')}
              getDecision={(id) => getDecision(id, queryParameters?.listing_type || 'RENT')}
            />
          </div>
        )}
      </>
    );
  };

  const renderMobileLayout = () => (
    <>
      {/* Full-screen map */}
      <div className="flex-1 relative min-h-0">
        {/* Streaming Progress Bar */}
        <div className="absolute top-0 left-0 right-0 z-10">
          <StreamingProgressBar progress={streamingProgress} isLoading={isLoading} />
        </div>

        {processedListingData && processedListingData.features.length > 0 ? (
          <Map
            listingData={processedListingData}
            queryParameters={queryParameters}
            effectiveMetric={effectiveMetric}
            onPropertyClick={handlePropertyClick}
            pois={userPOIs}
            isPickingPOI={poiPickerActive}
            onPoiLocationPick={handlePoiLocationPick}
            onCancelPoiPicking={handleCancelPoiPicking}
          />
        ) : (
          <div className="flex-1 flex items-center justify-center bg-muted/20 h-full">
            <div className="text-center p-8 max-w-md">
              {isLoading ? (
                <>
                  <div className="text-4xl mb-4 animate-pulse">🏠</div>
                  <h2 className="text-lg font-semibold mb-2">Loading...</h2>
                </>
              ) : (
                <>
                  <div className="text-4xl mb-4">🏠</div>
                  <h2 className="text-lg font-semibold mb-2">Property Explorer</h2>
                  <p className="text-muted-foreground text-sm">
                    Use the filter button to find properties.
                  </p>
                </>
              )}
            </div>
          </div>
        )}
      </div>

      {/* Filter & Review FABs */}
      <div className="fixed bottom-24 right-4 z-50 flex flex-col gap-2">
        <Button
          size="lg"
          variant="outline"
          className="rounded-full shadow-lg h-14 w-14 bg-background"
          onClick={() => setShowReviewMode(true)}
          disabled={!processedListingData || processedListingData.features.length === 0}
        >
          <Heart className="h-6 w-6" />
        </Button>
        <Sheet open={mobileFilterOpen} onOpenChange={setMobileFilterOpen}>
          <SheetTrigger asChild>
            <Button size="lg" className="rounded-full shadow-lg h-14 w-14">
              <Filter className="h-6 w-6" />
            </Button>
          </SheetTrigger>
          <SheetContent side="left" className="w-80 p-0">
            <div className="h-full flex flex-col">
              <div className="flex-1 min-h-0">
                <FilterPanel
                  onSubmit={onSubmit}
                  currentMetric={currentMetric}
                  isLoading={isLoading}
                  listingCount={processedListingData?.features.length}
                  user={user}
                  onTaskCreated={handlePOITaskCreated}
                  onStartPoiPicking={handleStartPoiPicking}
                  pickedPoiLocation={pickedPoiLocation}
                  userPOIs={userPOIs}
                  poiTravelFilters={poiTravelFilters}
                  onPoiTravelFiltersChange={setPoiTravelFilters}
                />
              </div>
              <div className="shrink-0 p-4">
                <VisualizationCard
                  metric={currentMetric}
                  onMetricChange={handleMetricChange}
                  userPOIs={userPOIs}
                  onPoiMetricChange={setPoiMetricSelection}
                />
              </div>
            </div>
          </SheetContent>
        </Sheet>
      </div>

      {/* Bottom Sheet */}
      {processedListingData && processedListingData.features.length > 0 && (
        <MobileBottomSheet
          listingData={processedListingData}
          onPropertyClick={handlePropertyClick}
          highlightedPropertyUrl={highlightedProperty}
          onActiveListingChange={handleActiveListingChange}
          poiMetricSelection={poiMetricSelection}
        />
      )}
    </>
  );

  const handlePOITaskCreated = (taskId: string) => {
    setExplicitTaskId(taskId);
    if (taskId) subscribe(taskId);
    // Refresh POI list in case new ones were created
    if (user) {
      fetchUserPOIs(user).then(setUserPOIs).catch(() => {});
    }
  };

  const handleStartPoiPicking = () => {
    setPoiPickerActive(true);
    setPickedPoiLocation(null);
  };

  const handlePoiLocationPick = (lat: number, lng: number) => {
    setPickedPoiLocation({ lat, lng });
    setPoiPickerActive(false);
  };

  const handleCancelPoiPicking = () => {
    setPoiPickerActive(false);
  };


  return (
    <div className="h-screen flex flex-col overflow-hidden">
      {/* Header */}
      <Header
        user={user}
        tasks={tasks}
        activeTaskId={activeTaskId}
        isConnected={isConnected}
        onCancelTask={cancelTask}
        onClearAllTasks={async () => {
          const result = await clearAllTasks();
          if (result) {
            handleTaskCancelled();
          }
          return result;
        }}
        onTaskCompleted={handleTaskCompleted}
      />

      {isMobile ? (
        renderMobileLayout()
      ) : (
        /* Desktop layout */
        <div className="flex-1 flex overflow-hidden min-h-0">
          {/* Filter Panel - Desktop (fixed sidebar) */}
          <div className="w-80 shrink-0 h-full overflow-hidden">
            <div className="h-full flex flex-col">
              <div className="flex-1 min-h-0">
                <FilterPanel
                  onSubmit={onSubmit}
                  currentMetric={currentMetric}
                  isLoading={isLoading}
                  listingCount={processedListingData?.features.length}
                  user={user}
                  onTaskCreated={handlePOITaskCreated}
                  onStartPoiPicking={handleStartPoiPicking}
                  pickedPoiLocation={pickedPoiLocation}
                  userPOIs={userPOIs}
                  poiTravelFilters={poiTravelFilters}
                  onPoiTravelFiltersChange={setPoiTravelFilters}
                />
              </div>
              <div className="shrink-0 p-4 border-r">
                <VisualizationCard
                  metric={currentMetric}
                  onMetricChange={handleMetricChange}
                  userPOIs={userPOIs}
                  onPoiMetricChange={setPoiMetricSelection}
                />
              </div>
            </div>
          </div>

          {/* Main View Area */}
          <div className="flex-1 flex flex-col overflow-hidden min-h-0">
            {/* Streaming Progress Bar */}
            <div className="relative shrink-0">
              <StreamingProgressBar progress={streamingProgress} isLoading={isLoading} />
            </div>

            {/* Map/List Container */}
            <div className="flex-1 flex overflow-hidden min-h-0">
              {renderMainContent()}
            </div>

            {/* Stats Bar */}
            {processedListingData && processedListingData.features.length > 0 && (
              <div className="shrink-0">
                <StatsBar
                  listingData={processedListingData}
                  viewMode={viewMode}
                  onViewModeChange={setViewMode}
                  likedCount={likedCount}
                />
              </div>
            )}
          </div>
        </div>
      )}

      {/* Swipe Review Mode Overlay */}
      {showReviewMode && processedListingData && (
        <SwipeReviewMode
          features={processedListingData.features}
          onDecide={decide}
          onClear={clear}
          onClose={() => setShowReviewMode(false)}
          onSelectListing={(id) => setSelectedListingId(id)}
          getDecision={getDecision}
        />
      )}

      {/* Listing Detail Bottom Sheet */}
      <ListingDetailSheet
        user={user}
        listingId={selectedListingId}
        listingType={(queryParameters?.listing_type || 'RENT') as 'RENT' | 'BUY'}
        onClose={() => setSelectedListingId(null)}
        onDecide={(id, decision, type) => decide(id, decision, type)}
        onClearDecision={(id, type) => clear(id, type)}
        currentDecision={selectedListingId ? getDecision(selectedListingId, queryParameters?.listing_type || 'RENT') : undefined}
      />

      {/* Error Dialog */}
      <AlertError message={submitError} open={alertDialogIsOpen} setIsOpen={setAlertDialogIsOpen} />
    </div>
  );
}

export default App;
