"use client";

/**
 * Rank Tracker page — daily keyword position monitoring.
 * Summary cards, sortable/filterable table with inline position chart,
 * movers section, distribution bar chart, add-keyword modal, and
 * on-demand rank-check trigger.
 */

import { useCallback, useEffect, useMemo, useState } from "react";
import { useBrand } from "@/context/brand-context";
import { useBackgroundTasks } from "@/lib/stores/task-store";
import {
  Crosshair, Plus, RefreshCw, Search, BarChart3, TrendingUp,
  AlertCircle, Loader2, Eye, ArrowUp, ArrowDown, MapPin,
} from "lucide-react";
import {
  ResponsiveContainer, BarChart, Bar, XAxis, YAxis, CartesianGrid,
  Tooltip as RTooltip, Cell, LabelList,
} from "recharts";

import AddKeywordsModal from "@/components/rank-tracker/add-keywords-modal";
import KeywordTable, { type KeywordRow } from "@/components/rank-tracker/keyword-table";
import MoversCards from "@/components/rank-tracker/movers-card";
import SuggestedKeywordsSection from "@/components/rank-tracker/suggested-keywords-section";
import FeatureGate from "@/components/plan/feature-gate";

// ─── Types ─────────────────────────────────────────────────────────────────────

interface SummaryData {
  total: number;
  averagePosition: number | null;
  top3: number;
  top10: number;
  top20: number;
  notRanking: number;
  visibilityScore: number;
  improved: Array<{ keyword: string; change: number; position: number | null }>;
  declined: Array<{ keyword: string; change: number; position: number | null }>;
  distribution: Array<{ label: string; count: number }>;
  deltas: {
    averagePosition: number | null;
    top3: number;
    top10: number;
    notRanking: number;
    visibilityScore: number;
    keywordsAdded: number;
  };
}

interface CheckResult {
  checked: number;
  improved: number;
  declined: number;
  newRankings: number;
  notFound: number;
}

/** Position-bucket → table filter mapping for click-to-filter on the
 *  Position Distribution chart. Mirrors the bucket labels in
 *  src/app/api/rank-tracking/summary/route.ts. */
const DISTRIBUTION_FILTER_MAP: Record<string, string> = {
  "1–3": "top3",
  "4–10": "top10",
  "11–20": "top20",
  "21–30": "all", // no dedicated filter — fall back to "all"
  "Not ranking": "not_ranking",
};

/** Bucket colour palette — keep in sync with KPI accent colours so the
 *  visual story is consistent across the page. */
const DISTRIBUTION_COLORS = ["#10B981", "#34D399", "#F59E0B", "#F97316", "#EF4444"];

// ─── Page ──────────────────────────────────────────────────────────────────────

// Rank Tracker is a Growth+ feature. FeatureGate wrapper catches
// direct navigations that bypass the sidebar's locked-click handler.
export default function RankTrackerPageGated() {
  return (
    <FeatureGate feature="rank_tracker">
      <RankTrackerPage />
    </FeatureGate>
  );
}

function RankTrackerPage() {
  const { brand } = useBrand();
  const { addTask, completeTask, failTask } = useBackgroundTasks();
  const [keywords, setKeywords] = useState<KeywordRow[]>([]);
  const [summary, setSummary] = useState<SummaryData | null>(null);
  const [loading, setLoading] = useState(true);
  const [checking, setChecking] = useState(false);
  const [addOpen, setAddOpen] = useState(false);
  const [query, setQuery] = useState("");
  const [tagFilter, setTagFilter] = useState("");
  const [positionFilter, setPositionFilter] = useState("all");
  const [deviceFilter, setDeviceFilter] = useState("all");
  const [locationFilter, setLocationFilter] = useState("all");

  // ── Fetch keywords + summary ─────────────────────────────────────
  const load = useCallback(async () => {
    if (!brand?.id) return;
    setLoading(true);
    try {
      const [kwRes, sumRes] = await Promise.all([
        fetch(`/api/rank-tracking/keywords?brandId=${brand.id}`),
        fetch(`/api/rank-tracking/summary?brandId=${brand.id}`),
      ]);
      if (kwRes.ok) { const d = await kwRes.json(); setKeywords(d.keywords || []); }
      if (sumRes.ok) { const d = await sumRes.json(); setSummary(d); }
    } finally { setLoading(false); }
  }, [brand.id]);

  useEffect(() => { load(); }, [load]);

  // ── Check now ────────────────────────────────────────────────────
  // The check route returns { checked, improved, declined, newRankings,
  // notFound } so we can build a meaningful toast instead of a flat
  // "Checked N" message. When everything stays put we say so explicitly
  // — silence is worse than "all positions unchanged" for a user who
  // pressed the button expecting feedback.
  async function checkNow() {
    setChecking(true);
    const taskId = addTask({ id: `rank_${Date.now()}`, type: "rank_check", label: "Checking positions…", brandId: brand.id, resultUrl: "/rank-tracker" });
    try {
      const res = await fetch("/api/rank-tracking/check", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ brandId: brand.id }),
      });
      const data = (await res.json().catch(() => null)) as CheckResult | null;
      await load();
      const parts: string[] = [];
      if (data) {
        parts.push(`Checked ${data.checked} keyword${data.checked === 1 ? "" : "s"}`);
        const movements: string[] = [];
        if (data.improved > 0) movements.push(`${data.improved} improved`);
        if (data.declined > 0) movements.push(`${data.declined} declined`);
        if (data.newRankings > 0) movements.push(`${data.newRankings} new`);
        if (movements.length > 0) parts.push(movements.join(", "));
        else if (data.checked > 0) parts.push("all positions unchanged");
        if (data.notFound > 0) parts.push(`${data.notFound} not found`);
      } else {
        parts.push(`Checked ${keywords.length} keywords`);
      }
      completeTask(taskId, { resultUrl: "/rank-tracker", resultSummary: parts.join(" · ") });
    } catch (err) {
      failTask(taskId, err instanceof Error ? err.message : "Rank check failed");
    } finally { setChecking(false); }
  }

  // ── GSC backfill ─────────────────────────────────────────────────
  // Seeds KeywordRanking from GscQuery for every active keyword. Used
  // when the user starts tracking after GSC already has months of
  // data — otherwise the position chart would be empty for the first
  // 90 days. Idempotent server-side, so running this twice is fine.
  const [backfilling, setBackfilling] = useState(false);
  async function backfillFromGsc() {
    setBackfilling(true);
    const taskId = addTask({ id: `backfill_${Date.now()}`, type: "rank_check", label: "Backfilling from GSC…", brandId: brand.id, resultUrl: "/rank-tracker" });
    try {
      const res = await fetch("/api/rank-tracking/backfill", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ brandId: brand.id }),
      });
      const data = await res.json().catch(() => ({ backfilled: 0, skipped: 0, keywordsTouched: 0, keywordsWithNoData: 0, noGscConnection: false }));
      await load();
      // Build a message that explains *why* a run produced zero rows. The
      // common confusing case: GSC returns no per-day history for the
      // keyword (either it has zero impressions or query-level data isn't
      // available), so the backfill silently does nothing. Surface that
      // explicitly instead of "Backfilled 0 across 0 keywords".
      let resultSummary: string;
      if (data.noGscConnection) {
        resultSummary = "Google Search Console isn't connected — connect GSC under Integrations to enable backfill.";
      } else if (data.backfilled > 0) {
        resultSummary = `Backfilled ${data.backfilled} history point${data.backfilled === 1 ? "" : "s"} across ${data.keywordsTouched} keyword${data.keywordsTouched === 1 ? "" : "s"}${data.skipped ? ` (${data.skipped} day${data.skipped === 1 ? "" : "s"} already covered)` : ""}.`;
      } else if (data.skipped > 0 && data.keywordsWithNoData === 0) {
        resultSummary = `Position history is already up to date (${data.skipped} day${data.skipped === 1 ? "" : "s"} covered). Daily checks will add new points going forward.`;
      } else {
        resultSummary = `No additional GSC history found. Google may not have query-level data for these keywords yet — daily position tracking will build history over time.`;
      }
      completeTask(taskId, { resultUrl: "/rank-tracker", resultSummary });
    } catch (err) {
      failTask(taskId, err instanceof Error ? err.message : "Backfill failed");
    } finally { setBackfilling(false); }
  }

  // ── Delete keywords ──────────────────────────────────────────────
  async function deleteKeywords(ids: string[]) {
    await Promise.all(ids.map((id) => fetch(`/api/rank-tracking/keywords/${id}`, { method: "DELETE" })));
    await load();
  }

  // ── onAdded handler for the Add Keywords modal ───────────────────
  // The POST handler fires a fire-and-forget GSC backfill per new
  // keyword (src/app/api/rank-tracking/keywords/route.ts). That work
  // takes ~1-3s per keyword to land in KeywordRanking, so calling
  // load() once immediately shows the new row but with an empty
  // history chart. Schedule a second reload ~3.5s later to pick up
  // the backfilled points without the user needing to hit refresh.
  function onKeywordsAdded() {
    load();
    const taskId = addTask({
      id: `kw_backfill_${Date.now()}`,
      type: "rank_check",
      label: "Backfilling history from GSC…",
      brandId: brand.id,
      resultUrl: "/rank-tracker",
    });
    setTimeout(async () => {
      try {
        await load();
        completeTask(taskId, { resultUrl: "/rank-tracker", resultSummary: "Position history loaded." });
      } catch (err) {
        failTask(taskId, err instanceof Error ? err.message : "Reload failed");
      }
    }, 3500);
  }

  // ── Unique tags for the filter dropdown ──────────────────────────
  const allTags = useMemo(() => {
    const tagSet = new Set<string>();
    keywords.forEach((k) => k.tags.forEach((t) => tagSet.add(t)));
    return Array.from(tagSet).sort();
  }, [keywords]);

  // Lowercase set of already-tracked keywords — passed to the
  // SuggestedKeywordsSection so it can hide entries that overlap with
  // the live tracked list (the suggestions API filters on the server
  // too, but the client-side filter prevents flicker between fetches).
  const trackedSet = useMemo(
    () => new Set(keywords.map((k) => k.keyword.toLowerCase())),
    [keywords],
  );

  // Unique locations from tracked keywords — for the By Location filter
  const uniqueLocations = useMemo(() => {
    const locs = new Set<string>();
    keywords.forEach((k) => { if (k.location) locs.add(k.location); });
    return Array.from(locs).sort();
  }, [keywords]);

  // ── Empty state ──────────────────────────────────────────────────
  if (!loading && keywords.length === 0) {
    return (
      <div className="space-y-6 max-w-5xl">
        <div>
          <h1 className="text-2xl font-bold text-slate-900">Rank Tracker</h1>
          <p className="text-slate-500 text-sm mt-1">Daily keyword position monitoring for {brand.domain}</p>
        </div>
        <div className="bg-white rounded-xl border border-slate-200 p-12 text-center">
          <Crosshair className="w-14 h-14 text-slate-200 mx-auto mb-4" />
          <h2 className="text-lg font-bold text-slate-900 mb-1">Start Tracking Keywords</h2>
          <p className="text-sm text-slate-500 max-w-md mx-auto mb-6">
            Add keywords to monitor your daily search rankings and track position changes over time.
          </p>
          <button onClick={() => setAddOpen(true)} className="inline-flex items-center gap-1.5 px-5 py-2.5 bg-brand-600 text-white text-sm font-semibold rounded-xl hover:bg-brand-700">
            <Plus className="w-4 h-4" /> Add Keywords
          </button>
        </div>
        <AddKeywordsModal brandId={brand.id} open={addOpen} onClose={() => setAddOpen(false)} onAdded={onKeywordsAdded} />
      </div>
    );
  }

  // ── Loading state ────────────────────────────────────────────────
  if (loading) {
    return <div className="flex items-center justify-center h-64"><div className="w-8 h-8 border-4 border-brand-100 border-t-brand-600 rounded-full animate-spin" /></div>;
  }

  const avgPosColor = !summary?.averagePosition ? "text-slate-400" : summary.averagePosition <= 10 ? "text-green-600" : summary.averagePosition <= 20 ? "text-amber-600" : "text-red-500";
  const showSuggestions = !loading && keywords.length > 0 && keywords.length < 5;

  return (
    <div className="space-y-6 max-w-6xl">

      {/* Header */}
      <div className="flex items-start justify-between">
        <div>
          <h1 className="text-2xl font-bold text-slate-900">Rank Tracker</h1>
          <p className="text-slate-500 text-sm mt-1">{brand.domain} · {summary?.total ?? 0} keywords tracked</p>
        </div>
        <div className="flex items-center gap-2">
          <button
            onClick={backfillFromGsc}
            disabled={backfilling}
            className="flex items-center gap-1.5 px-4 py-2 rounded-lg border border-slate-200 text-xs font-semibold text-slate-700 hover:bg-slate-50 disabled:opacity-60"
            title="Seed position history for every tracked keyword from Google Search Console data already in the database. Idempotent — only fills dates that aren't already covered."
          >
            {backfilling ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <BarChart3 className="w-3.5 h-3.5" />}
            {backfilling ? "Backfilling…" : "Backfill from GSC"}
          </button>
          <button onClick={checkNow} disabled={checking} className="flex items-center gap-1.5 px-4 py-2 rounded-lg border border-slate-200 text-xs font-semibold text-slate-700 hover:bg-slate-50 disabled:opacity-60">
            {checking ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <RefreshCw className="w-3.5 h-3.5" />}
            {checking ? "Checking…" : "Check Now"}
          </button>
          <button onClick={() => setAddOpen(true)} className="flex items-center gap-1.5 px-4 py-2 bg-brand-600 text-white text-xs font-semibold rounded-lg hover:bg-brand-700">
            <Plus className="w-3.5 h-3.5" /> Add Keywords
          </button>
        </div>
      </div>

      {/* Summary cards — 6 KPIs with 7-day deltas. The Visibility Score
          is sum(1/position)/n * 100 for keywords with position 1-100,
          giving a single number that goes UP when rankings improve. */}
      {summary && (
        <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3">
          <SummaryCard
            label="Total Keywords"
            value={String(summary.total)}
            icon={Crosshair}
            delta={summary.deltas.keywordsAdded}
            deltaLabel={`${summary.deltas.keywordsAdded > 0 ? "+" : ""}${summary.deltas.keywordsAdded} this week`}
            higherIsBetter
          />
          <SummaryCard
            label="Avg Position"
            value={summary.averagePosition != null ? String(summary.averagePosition) : checking ? "Checking…" : "—"}
            valueHint={summary.averagePosition == null && !checking ? "Awaiting first check" : null}
            icon={TrendingUp}
            valueClass={avgPosColor}
            delta={summary.deltas.averagePosition}
            // Lower position number = better rank, so we flip the colour.
            higherIsBetter={false}
          />
          <SummaryCard
            label="Visibility Score"
            value={String(summary.visibilityScore)}
            icon={Eye}
            valueClass={summary.visibilityScore >= 50 ? "text-green-600" : summary.visibilityScore >= 25 ? "text-amber-600" : "text-slate-700"}
            delta={summary.deltas.visibilityScore}
            higherIsBetter
            tooltip="0–100 score. 100 = every tracked keyword at #1. Computed as the average inverse position across keywords ranking 1–100."
          />
          <SummaryCard
            label="Top 3"
            value={String(summary.top3)}
            icon={TrendingUp}
            valueClass="text-green-600"
            delta={summary.deltas.top3}
            higherIsBetter
          />
          <SummaryCard
            label="Top 10"
            value={String(summary.top10)}
            icon={BarChart3}
            valueClass="text-blue-600"
            delta={summary.deltas.top10}
            higherIsBetter
          />
          <SummaryCard
            label="Not Ranking"
            value={String(summary.notRanking)}
            icon={AlertCircle}
            valueClass={summary.notRanking > 0 ? "text-red-500" : "text-slate-400"}
            delta={summary.deltas.notRanking}
            higherIsBetter={false}
          />
        </div>
      )}

      {/* Filters */}
      <div className="flex items-center gap-2 flex-wrap">
        <div className="relative flex-1 min-w-[200px]">
          <Search className="w-3.5 h-3.5 text-slate-400 absolute left-3 top-1/2 -translate-y-1/2" />
          <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search keywords…" className="w-full pl-9 pr-3 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500/40 focus:border-brand-500" />
        </div>
        {allTags.length > 0 && (
          <select value={tagFilter} onChange={(e) => setTagFilter(e.target.value)} className="text-sm border border-slate-200 rounded-lg px-3 py-2 bg-white">
            <option value="">All tags</option>
            {allTags.map((t) => <option key={t} value={t}>{t}</option>)}
          </select>
        )}
        <select value={positionFilter} onChange={(e) => setPositionFilter(e.target.value)} className="text-sm border border-slate-200 rounded-lg px-3 py-2 bg-white">
          <option value="all">All positions</option>
          <option value="top3">Top 3</option>
          <option value="top10">Top 10</option>
          <option value="top20">Top 20</option>
          <option value="not_ranking">Not ranking</option>
        </select>
        <select value={deviceFilter} onChange={(e) => setDeviceFilter(e.target.value)} className="text-sm border border-slate-200 rounded-lg px-3 py-2 bg-white">
          <option value="all">All devices</option>
          <option value="desktop">Desktop</option>
          <option value="mobile">Mobile</option>
        </select>
      </div>

      {/* By Location filter chips — single-location: fixed info chip; multi-location: filterable */}
      {uniqueLocations.length === 1 && (
        <div className="flex items-center gap-1.5">
          <MapPin className="w-3 h-3 text-slate-400" />
          <span className="text-[11px] text-slate-500">Location:</span>
          <span className="inline-flex items-center gap-1 text-[11px] font-semibold px-2.5 py-1 rounded-full bg-slate-100 text-slate-600 border border-slate-200">
            {uniqueLocations[0].replace("United States", "US")}
          </span>
        </div>
      )}
      {uniqueLocations.length > 1 && (
        <div className="flex items-center gap-1.5 flex-wrap">
          <MapPin className="w-3 h-3 text-slate-400 shrink-0" />
          <span className="text-[11px] text-slate-500 shrink-0">Filter by location:</span>
          <button
            onClick={() => setLocationFilter("all")}
            className={`text-[11px] font-semibold px-2.5 py-1 rounded-full border transition-colors ${locationFilter === "all" ? "bg-brand-600 text-white border-brand-600" : "bg-white text-slate-600 border-slate-200 hover:bg-slate-50"}`}
          >
            All
          </button>
          {uniqueLocations.map((loc) => (
            <button
              key={loc}
              onClick={() => setLocationFilter(locationFilter === loc ? "all" : loc)}
              className={`text-[11px] font-semibold px-2.5 py-1 rounded-full border transition-colors ${locationFilter === loc ? "bg-brand-600 text-white border-brand-600" : "bg-white text-slate-600 border-slate-200 hover:bg-slate-50"}`}
            >
              {loc.replace("United States", "US")}
            </button>
          ))}
        </div>
      )}

      {/* Keywords table */}
      <KeywordTable keywords={keywords} query={query} tagFilter={tagFilter} positionFilter={positionFilter} deviceFilter={deviceFilter} locationFilter={locationFilter} onDelete={deleteKeywords} />

      {/* Movers */}
      {summary && <MoversCards improved={summary.improved} declined={summary.declined} />}

      {/* Distribution chart — clicking a bar filters the keyword table
          to only that position bucket. Colours run green→amber→red so
          a glance reveals overall ranking health. */}
      {summary && summary.distribution.some((d) => d.count > 0) && (
        <div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
          <div className="px-5 py-3.5 border-b border-slate-100 flex items-center justify-between">
            <div className="flex items-center gap-2">
              <BarChart3 className="w-4 h-4 text-brand-600" />
              <h2 className="font-bold text-slate-800 text-sm">Position Distribution</h2>
            </div>
            <span className="text-[10px] text-slate-400">Click a bar to filter the table</span>
          </div>
          <div className="px-3 pt-3 pb-2 h-52">
            <ResponsiveContainer width="100%" height="100%">
              <BarChart data={summary.distribution} margin={{ top: 14, right: 8, bottom: 0, left: 0 }}>
                <CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" vertical={false} />
                <XAxis dataKey="label" tick={{ fontSize: 11 }} stroke="#94a3b8" />
                <YAxis tick={{ fontSize: 11 }} stroke="#94a3b8" allowDecimals={false} />
                <RTooltip contentStyle={{ fontSize: "12px", borderRadius: "8px", border: "1px solid #e2e8f0" }} />
                <Bar
                  dataKey="count"
                  radius={[6, 6, 0, 0]}
                  cursor="pointer"
                  onClick={(_data, index) => {
                    const bucket = summary.distribution[index];
                    const next = bucket ? DISTRIBUTION_FILTER_MAP[bucket.label] ?? "all" : "all";
                    setPositionFilter((prev) => (prev === next ? "all" : next));
                  }}
                >
                  <LabelList
                    dataKey="count"
                    position="top"
                    fill="#475569"
                    fontSize={11}
                    fontWeight={700}
                    formatter={(v: unknown) => (typeof v === "number" && v > 0 ? String(v) : "")}
                  />
                  {summary.distribution.map((_, i) => (
                    <Cell key={i} fill={DISTRIBUTION_COLORS[i % DISTRIBUTION_COLORS.length]} />
                  ))}
                </Bar>
              </BarChart>
            </ResponsiveContainer>
          </div>
        </div>
      )}

      {/* Suggested keywords — shown when the brand has fewer than 5
          keywords tracked. Pulls from /api/rank-tracking/suggestions
          (GSC-derived, AI-skipped for speed) and offers bulk-add. */}
      {showSuggestions && (
        <SuggestedKeywordsSection
          brandId={brand.id}
          alreadyTracked={trackedSet}
          onAdded={onKeywordsAdded}
        />
      )}

      <AddKeywordsModal brandId={brand.id} open={addOpen} onClose={() => setAddOpen(false)} onAdded={onKeywordsAdded} />
    </div>
  );
}

// ─── Summary card ──────────────────────────────────────────────────────────────

function SummaryCard({
  label, value, valueHint, icon: Icon, valueClass, delta, deltaLabel, higherIsBetter, tooltip,
}: {
  label: string;
  value: string;
  /** Subtitle below the value when no real number is available (e.g. "Awaiting first check"). */
  valueHint?: string | null;
  icon: typeof Crosshair;
  valueClass?: string;
  /** 7-day delta. Sign-only — positive means "went up", negative means "went down". */
  delta?: number | null;
  /** Override the auto-rendered delta string ("+2", "▲ 0.3" etc.). */
  deltaLabel?: string;
  /** When true, positive delta = good (green). When false, negative delta = good. */
  higherIsBetter?: boolean;
  tooltip?: string;
}) {
  // Render a chip with arrow + value. Hidden when delta is null/0/undefined.
  let deltaChip: React.ReactNode = null;
  if (delta != null && delta !== 0) {
    const isImprovement = higherIsBetter ? delta > 0 : delta < 0;
    const colour = isImprovement ? "text-green-600" : "text-red-500";
    const Arrow = delta > 0 ? ArrowUp : ArrowDown;
    const text = deltaLabel ?? `${delta > 0 ? "+" : ""}${Math.round(delta * 10) / 10}`;
    deltaChip = (
      <span className={`flex items-center gap-0.5 text-[10px] font-bold tabular-nums ${colour}`}>
        <Arrow className="w-3 h-3" />
        {text}
      </span>
    );
  } else if (deltaLabel && delta === 0) {
    deltaChip = <span className="text-[10px] font-bold text-slate-400 tabular-nums">{deltaLabel}</span>;
  }

  return (
    <div className="bg-white rounded-xl border border-slate-200 px-4 py-3.5" title={tooltip}>
      <div className="flex items-center justify-between mb-1.5">
        <p className="text-[10px] font-bold uppercase tracking-widest text-slate-400">{label}</p>
        <Icon className="w-3.5 h-3.5 text-slate-300" />
      </div>
      <div className="flex items-end justify-between gap-2">
        <p className={`text-2xl font-bold tabular-nums ${valueClass || "text-slate-900"}`}>{value}</p>
        {deltaChip}
      </div>
      {valueHint && <p className="text-[10px] text-slate-400 mt-0.5">{valueHint}</p>}
    </div>
  );
}
