import { useMemo, useState } from "react";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { Loader2, Building2, MapPin, ArrowRight, Star, Sparkles, Tag, TrendingUp, TrendingDown, Minus, Crosshair as CrosshairIcon } from "lucide-react";
import { AppShell } from "@/components/AppShell";
import { PortfolioPrompt } from "@/components/PortfolioPrompt";
import { PageHeader } from "@/components/PageHeader";
import { BriefingSkeleton } from "@/components/BriefingSkeleton";
import { Button } from "@/components/ui/button";

import { CompetitorFocus } from "@/components/CompetitorFocus";
import { CompetitorFilters, DEFAULT_FILTERS, type CompetitorFilterState } from "@/components/CompetitorFilters";
import { useBriefing } from "@/hooks/useBriefing";
import { useT } from "@/hooks/useSettings";
import type { Competitor } from "@/lib/briefing.functions";

export const Route = createFileRoute("/competitors")({
  head: () => ({
    meta: [
      { title: "Competitors — The Daily Ledger" },
      {
        name: "description",
        content: "Nearby competing hotels — pricing signals, recent moves, and threat level.",
      },
      { property: "og:title", content: "Competitors — The Daily Ledger" },
      {
        property: "og:description",
        content: "Pricing, occupancy and recent moves of nearby competing hotels.",
      },
    ],
  }),
  component: CompetitorsPage,
});

function CompetitorsPage() {
  const [filters, setFilters] = useState<CompetitorFilterState>(() =>
    DEFAULT_FILTERS(5),
  );
  // Trigger a fresh AI scan whenever the user changes scope (radius or travel time)
  // in manual mode — otherwise we'd only re-filter the cached list.
  const isManual = filters.mode === "manual";
  const competitorRadiusOverride = isManual && filters.searchMode === "distance" ? filters.radiusKm : undefined;
  const competitorSearchMode = isManual ? filters.searchMode : "distance";
  const competitorTravelTimeMin = isManual && filters.searchMode === "time" ? filters.travelTimeMin : undefined;
  const { hotel, isAllHotels, competitorsData, isHotelLoading, isCompetitorsLoading, competitorsError, refresh } = useBriefing({
    competitorRadiusOverride,
    competitorSearchMode,
    competitorTravelTimeMin,
  });
  const error = competitorsError;
  const briefing = competitorsData;
  const isBriefingLoading = isCompetitorsLoading;
  const t = useT();
  const [focusedKey, setFocusedKey] = useState<string | null>(null);

  if (isHotelLoading) {
    return (
      <div className="flex min-h-dvh items-center justify-center">
        <Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
      </div>
    );
  }
  if (isAllHotels || !hotel) return <PortfolioPrompt pageTitle={t("nav.competitors")} />;

  const currency = hotel.currency || "MYR";
  const myAdr = hotel.adr_weekday ?? undefined;

  const allCompetitors = (briefing?.competitors ?? []).filter(
    (c) => !isSameProperty(c.name, hotel.hotel_name),
  );
  const visibleCompetitors = applyFilters(allCompetitors, filters, myAdr);
  const sortedAll = sortCompetitors(visibleCompetitors, filters.sortBy, myAdr);
  const sortedCompetitors = sortedAll.slice(0, filters.maxResults);
  const topMatchCount = sortedCompetitors.filter((c) => c.is_top_match || (c.match_score ?? 0) >= 75).length;
  const isTopMatch = (c: Competitor) => c.is_top_match || (c.match_score ?? 0) >= 75;

  const profileText = `${hotel.star_rating ? `${hotel.star_rating}★ ` : ""}${hotel.segment ?? t("competitors.profile.default")}`;

  return (
    <AppShell hotel={hotel} isLoading={isBriefingLoading} onRefresh={refresh}>
      <PageHeader
        eyebrow={t("competitors.eyebrow")}
        title={t("competitors.title", { location: hotel.location })}
        subtitle={
          isBriefingLoading || !briefing
            ? t("competitors.subtitle.scanning", { km: filters.mode === "manual" ? filters.radiusKm : hotel.radius_km, hotel: hotel.hotel_name })
            : t("competitors.subtitle.done", { n: sortedCompetitors.length, km: filters.mode === "manual" ? filters.radiusKm : hotel.radius_km, hotel: hotel.hotel_name, top: topMatchCount, profile: profileText })
        }
      >
        <div className="inline-flex items-center gap-1.5 text-xs text-muted-foreground">
          <MapPin className="h-3.5 w-3.5" />
          {isManual && filters.searchMode === "time"
            ? `Within ${filters.travelTimeMin} min drive`
            : t("competitors.within", { km: isManual ? filters.radiusKm : (hotel.radius_km ?? 5) })}
        </div>
      </PageHeader>

      {error ? (
        <div className="rounded-2xl border border-destructive/30 bg-destructive/5 p-6 text-sm text-destructive">
          <strong className="font-semibold">{t("competitors.error")}</strong>
          <p className="mt-1 text-destructive/80">{(error as Error).message}</p>
          <Button size="sm" variant="outline" className="mt-4" onClick={refresh}>
            {t("common.tryAgain")}
          </Button>
        </div>
      ) : isBriefingLoading || !briefing ? (
        <BriefingSkeleton />
      ) : (
        <>
          {(() => {
            const focused = focusedKey
              ? sortedCompetitors.find((c) => focusKey(c) === focusedKey)
              : null;
            return focused ? (
              <CompetitorFocus
                hotel={hotel}
                competitor={focused}
                currency={currency}
                onClose={() => setFocusedKey(null)}
              />
            ) : null;
          })()}

          <CompetitorFilters
            state={filters}
            onChange={setFilters}
            defaultRadiusKm={hotel.radius_km}
            totalCount={allCompetitors.length}
            visibleCount={sortedCompetitors.length}
          />

          <div className="mb-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
            <SummaryStat
              label={t("competitors.stat.scanned")}
              value={`${sortedCompetitors.length}`}
            />
            <SummaryStat
              label={t("competitors.stat.adr", { c: currency })}
              value={`${currency} ${avg(sortedCompetitors.map((c) => c.estimated_adr_myr)).toFixed(0)}`}
            />
            <SummaryStat
              label={t("competitors.stat.occ")}
              value={`${avg(sortedCompetitors.map((c) => c.estimated_occupancy_pct)).toFixed(0)}%`}
            />
            <SummaryStat
              label={t("competitors.stat.promo")}
              value={`${sortedCompetitors.reduce((n, c) => n + (c.promotions?.length ?? 0), 0)}`}
              accent
            />
          </div>

          {sortedCompetitors.length > 0 && (
            <section>
              <div className="mb-4 flex flex-wrap items-end justify-between gap-3">
                <div className="min-w-0">
                  <h2 className="font-serif text-xl font-medium text-foreground sm:text-2xl">
                    {t("competitors.section.ranked", { sort: sortLabel(filters.sortBy) })}
                  </h2>
                  <p className="mt-1 text-sm text-muted-foreground">
                    {t("competitors.section.ranked.desc")}
                  </p>
                </div>
                {topMatchCount > 0 && (
                  <span className="shrink-0 rounded-full bg-brand/10 px-3 py-1 text-xs font-medium text-brand">
                    {t("competitors.topMatches", { n: topMatchCount })}
                  </span>
                )}
              </div>
              <div className="grid gap-5 md:grid-cols-2">
                {sortedCompetitors.map((c, i) => (
                  <CompetitorCard
                    key={`c-${i}`}
                    c={c}
                    rank={i + 1}
                    currency={currency}
                    myAdr={hotel.adr_weekday ?? undefined}
                    highlighted={isTopMatch(c)}
                    isFocused={focusedKey === focusKey(c)}
                    onFocus={() => setFocusedKey(focusKey(c))}
                  />
                ))}
              </div>
            </section>
          )}
        </>
      )}
    </AppShell>
  );
}

function applyFilters(
  list: Competitor[],
  f: CompetitorFilterState,
  myAdr: number | undefined,
): Competitor[] {
  return list.filter((c) => {
    // Min match score applies in BOTH auto and manual modes
    if (f.minMatchScore > 0 && (c.match_score ?? 0) < f.minMatchScore) return false;
    if (f.mode === "auto") return true;
    if (f.searchMode === "time") {
      if (typeof c.travel_time_min === "number" && c.travel_time_min > f.travelTimeMin) return false;
    } else {
      if (c.distance_km > f.radiusKm) return false;
    }
    if (f.minStar > 0) {
      if (typeof c.star_rating !== "number") return false;
      if (f.minStar === 5 ? c.star_rating < 5 : c.star_rating < f.minStar) return false;
    }
    if (f.pricePosition !== "any") {
      const pos = derivePricePosition(c, myAdr);
      if (pos !== f.pricePosition) return false;
    }
    return true;
  });
}

function sortCompetitors(
  list: Competitor[],
  key: CompetitorSortKey,
  myAdr: number | undefined,
): Competitor[] {
  const arr = [...list];
  const threatRank = { high: 3, medium: 2, low: 1 } as const;
  switch (key) {
    case "distance":
      return arr.sort((a, b) => a.distance_km - b.distance_km);
    case "threat":
      return arr.sort((a, b) => threatRank[b.threat_level] - threatRank[a.threat_level]);
    case "rating":
      return arr.sort((a, b) => (b.star_rating ?? 0) - (a.star_rating ?? 0));
    case "price_near":
      if (!myAdr) return arr;
      return arr.sort(
        (a, b) =>
          Math.abs(a.estimated_adr_myr - myAdr) - Math.abs(b.estimated_adr_myr - myAdr),
      );
    case "most_match":
    default:
      return arr.sort((a, b) => (b.match_score ?? 0) - (a.match_score ?? 0));
  }
}

function derivePricePosition(
  c: Competitor,
  myAdr: number | undefined,
): "below" | "inline" | "above" | null {
  if (typeof myAdr === "number" && myAdr > 0) {
    if (c.estimated_adr_myr > myAdr * 1.05) return "above";
    if (c.estimated_adr_myr < myAdr * 0.95) return "below";
    return "inline";
  }
  return c.price_position ?? null;
}

type CompetitorSortKey = CompetitorFilterState["sortBy"];

function sortLabel(key: CompetitorSortKey): string {
  switch (key) {
    case "distance": return "closest first";
    case "threat": return "threat level";
    case "rating": return "star rating";
    case "price_near": return "price closest to yours";
    case "most_match":
    default: return "best match";
  }
}

function focusKey(c: Competitor): string {
  return `${c.name}|${c.distance_km.toFixed(2)}`;
}

function normalizeName(s: string): string {
  return s
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, " ")
    .replace(/\b(hotel|resort|the|by|and|kuala|lumpur|kl|sentral|spa|suites?|apartments?)\b/g, " ")
    .replace(/\s+/g, " ")
    .trim();
}

function isSameProperty(a: string, b: string): boolean {
  const na = normalizeName(a);
  const nb = normalizeName(b);
  if (!na || !nb) return false;
  if (na === nb) return true;
  // catch near-duplicates: one fully contains the other (>=4 chars)
  if (na.length >= 4 && nb.length >= 4 && (na.includes(nb) || nb.includes(na))) return true;
  return false;
}

function avg(xs: number[]): number {
  if (xs.length === 0) return 0;
  return xs.reduce((a, b) => a + b, 0) / xs.length;
}

function SummaryStat({
  label,
  value,
  accent,
}: {
  label: string;
  value: string;
  accent?: boolean;
}) {
  return (
    <div
      className={`rounded-2xl border p-5 shadow-[var(--shadow-elegant)] ${
        accent ? "bg-accent/10" : "bg-card"
      }`}
    >
      <div className="text-[11px] uppercase tracking-wider text-muted-foreground">{label}</div>
      <div className="mt-1 font-serif text-3xl font-medium text-brand">{value}</div>
    </div>
  );
}

function CompetitorCard({
  c,
  rank,
  currency,
  myAdr,
  highlighted,
  isFocused,
  onFocus,
}: {
  c: Competitor;
  rank?: number;
  currency: string;
  myAdr?: number;
  highlighted?: boolean;
  isFocused?: boolean;
  onFocus?: () => void;
}) {
  const t = useT();
  const navigate = useNavigate();
  const derived: "below" | "inline" | "above" | null =
    typeof myAdr === "number" && myAdr > 0
      ? c.estimated_adr_myr > myAdr * 1.05
        ? "above"
        : c.estimated_adr_myr < myAdr * 0.95
          ? "below"
          : "inline"
      : c.price_position ?? null;

  const score = typeof c.match_score === "number" ? Math.round(c.match_score) : null;
  const scoreCls =
    score == null
      ? "bg-muted text-muted-foreground"
      : score >= 80
        ? "bg-emerald-500/15 text-emerald-700"
        : score >= 60
          ? "bg-amber-500/15 text-amber-700"
          : "bg-muted text-muted-foreground";

  const subjectKey = `${c.name}|${c.distance_km.toFixed(2)}`;
  const context = `Competitor: ${c.name} — ${c.distance_km.toFixed(1)} km away.
${c.star_rating ? `${c.star_rating}★ ` : ""}${c.property_type ?? ""}${c.target_guest ? `, targets ${c.target_guest}` : ""}.
Positioning: ${c.positioning}
Estimated ADR: ${currency} ${c.estimated_adr_myr.toFixed(0)}; estimated occupancy: ${c.estimated_occupancy_pct.toFixed(0)}%.
Recent move: ${c.recent_move}
Threat level: ${c.threat_level}.
Unique offerings: ${(c.unique_offerings ?? []).join("; ") || "n/a"}
Active promotions: ${(c.promotions ?? []).map((p) => `${p.title} (${p.channel}${p.ends ? `, ends ${p.ends}` : ""})`).join("; ") || "none"}
Reviews: ${c.review_score ?? "?"}/10 from ${c.review_count ?? "?"} on ${c.review_source ?? "?"}.
${myAdr ? `User's hotel weekday ADR: ${currency} ${myAdr}.` : ""}`;

  const open = () =>
    navigate({
      to: "/deep-dive",
      search: { topic: "competitor", subjectKey, title: c.name, context },
    });

  return (
    <article
      className={`rounded-2xl border p-4 shadow-[var(--shadow-elegant)] sm:p-6 ${
        highlighted ? "border-brand/40 bg-brand-soft/20 ring-1 ring-brand/20" : "bg-card"
      }`}
    >
      <div className="mb-3 flex items-start justify-between gap-3">
        <div className="min-w-0">
          <div className="inline-flex flex-wrap items-center gap-x-2 gap-y-1 text-xs uppercase tracking-wider text-muted-foreground">
            {typeof rank === "number" && (
              <span className="inline-flex items-center justify-center rounded-full bg-brand/10 px-2 py-0.5 text-[10px] font-semibold text-brand">
                #{rank}
              </span>
            )}
            <span className="inline-flex items-center gap-1">
              <Building2 className="h-3 w-3" /> {t("competitors.kmAway", { km: c.distance_km.toFixed(1) })}
            </span>
            {typeof c.travel_time_min === "number" && (
              <span className="inline-flex items-center gap-1 opacity-80">
                • {Math.round(c.travel_time_min)} min drive
              </span>
            )}
            {typeof c.star_rating === "number" && (
              <span className="inline-flex items-center gap-0.5 text-amber-600">
                {Array.from({ length: Math.round(c.star_rating) }).map((_, i) => (
                  <Star key={i} className="h-3 w-3 fill-current" />
                ))}
              </span>
            )}
            {c.property_type && <span className="opacity-70">• {c.property_type}</span>}
            {c.target_guest && <span className="opacity-70">• {c.target_guest}</span>}
          </div>
          <h3 className="mt-1 break-words font-serif text-xl font-medium text-foreground">{c.name}</h3>
        </div>
        <div className="flex shrink-0 flex-col items-end gap-1.5">
          <ThreatBadge level={c.threat_level} />
          {score != null && (
            <span className={`rounded-full px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider ${scoreCls}`}>
              {t("competitors.match", { n: score })}
            </span>
          )}
        </div>
      </div>
      <p className="text-sm italic leading-relaxed text-muted-foreground">{c.positioning}</p>

      <div className="mt-4 grid grid-cols-3 gap-3 rounded-xl bg-muted/40 p-3">
        <div>
          <div className="text-[10px] uppercase tracking-wider text-muted-foreground">{t("competitors.estAdr")}</div>
          <div className="font-serif text-lg font-medium text-brand">
            {currency} {c.estimated_adr_myr.toFixed(0)}
          </div>
        </div>
        <div>
          <div className="text-[10px] uppercase tracking-wider text-muted-foreground">{t("competitors.occupancy")}</div>
          <div className="font-serif text-lg font-medium text-brand">
            {c.estimated_occupancy_pct.toFixed(0)}%
          </div>
        </div>
        <div>
          <div className="text-[10px] uppercase tracking-wider text-muted-foreground">{t("competitors.vsYou")}</div>
          <PricePositionPill position={derived} />
        </div>
      </div>

      {c.recent_move ? (
        <div className="mt-3 flex items-start gap-2 rounded-lg bg-brand-soft/40 px-3 py-2 text-sm text-brand">
          <ArrowRight className="mt-0.5 h-3.5 w-3.5 shrink-0" />
          <span>
            <span className="font-medium">{t("competitors.recentMove")}</span> {c.recent_move}
          </span>
        </div>
      ) : null}

      <div className="mt-4 flex flex-wrap gap-2">
        <Button size="sm" variant="default" onClick={open}>
          <Sparkles className="mr-1.5 h-3.5 w-3.5" />
          {t("competitors.viewDetails")}
        </Button>
        {onFocus ? (
          <Button
            size="sm"
            variant={isFocused ? "secondary" : "outline"}
            onClick={() => {
              onFocus();
              if (typeof window !== "undefined") window.scrollTo({ top: 0, behavior: "smooth" });
            }}
          >
            <CrosshairIcon className="mr-1.5 h-3.5 w-3.5" />
            {isFocused ? "Focused" : "Focus & compare"}
          </Button>
        ) : null}
      </div>
    </article>
  );
}

function PricePositionPill({ position }: { position: "below" | "inline" | "above" | null }) {
  if (!position) return <span className="font-serif text-lg font-medium text-muted-foreground">—</span>;
  const cfg = {
    below: { Icon: TrendingDown, label: "Below", cls: "text-emerald-700" },
    inline: { Icon: Minus, label: "Inline", cls: "text-muted-foreground" },
    above: { Icon: TrendingUp, label: "Above", cls: "text-destructive" },
  }[position];
  const { Icon, label, cls } = cfg;
  return (
    <span className={`inline-flex items-center gap-1 font-serif text-lg font-medium ${cls}`}>
      <Icon className="h-4 w-4" /> {label}
    </span>
  );
}

function ThreatBadge({ level }: { level: "low" | "medium" | "high" }) {
  const styles = {
    low: "bg-emerald-500/10 text-emerald-700 border-emerald-500/30",
    medium: "bg-amber-500/10 text-amber-700 border-amber-500/30",
    high: "bg-destructive/10 text-destructive border-destructive/30",
  } as const;
  return (
    <span
      className={`shrink-0 rounded-full border px-2.5 py-0.5 text-[10px] font-medium uppercase tracking-wider ${styles[level]}`}
    >
      {level} threat
    </span>
  );
}
