"use client";

import { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { CheckIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import {
  AddressCandidate,
  PostcodeGeo,
  describeCandidate,
  normalisePostcode,
} from "@/lib/postcodeSearch/model";

type SearchCandidate = AddressCandidate & { alreadyInPortfolio: boolean };

interface SearchResponse {
  postcode: string;
  geo: PostcodeGeo;
  source: "cache" | "live";
  cacheAgeDays: number;
  candidates: SearchCandidate[];
}

// Selections survive across searches so a user can gather several postcodes
// before submitting once; each group keeps its own geography for the POST.
type Basket = Record<
  string,
  { geo: PostcodeGeo; byUprn: Record<string, SearchCandidate> }
>;

const countSelected = (basket: Basket) =>
  Object.values(basket).reduce((n, g) => n + Object.keys(g.byUprn).length, 0);

export default function AddPropertiesClient({
  portfolioId,
  portfolioName,
}: {
  portfolioId: string;
  portfolioName: string;
}) {
  const router = useRouter();
  const queryClient = useQueryClient();
  const [input, setInput] = useState("");
  const [inputError, setInputError] = useState<string | null>(null);
  const [search, setSearch] = useState<{ pc: string; refresh: number } | null>(null);
  const [filter, setFilter] = useState("");
  const [basket, setBasket] = useState<Basket>({});
  const [done, setDone] = useState<{ added: number; skipped: number } | null>(null);

  const query = useQuery<SearchResponse, Error>({
    queryKey: ["postcode-search", portfolioId, search?.pc, search?.refresh],
    enabled: !!search,
    staleTime: 5 * 60 * 1000,
    retry: false,
    queryFn: async () => {
      const params = new URLSearchParams({ postcode: search!.pc });
      if (search!.refresh > 0) params.set("refresh", "1");
      const res = await fetch(
        `/api/portfolio/${portfolioId}/properties/search?${params}`,
      );
      const body = await res.json();
      if (!res.ok) throw new Error(body.error ?? "Search failed");
      return body as SearchResponse;
    },
  });

  const submit = useMutation<
    { added: number; skipped: number },
    Error,
    Basket
  >({
    mutationFn: async (toSubmit) => {
      let added = 0;
      let skipped = 0;
      for (const [, group] of Object.entries(toSubmit)) {
        const res = await fetch(`/api/portfolio/${portfolioId}/properties`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            geo: group.geo,
            candidates: Object.values(group.byUprn),
          }),
        });
        const body = await res.json();
        if (!res.ok) throw new Error(body.error ?? "Adding properties failed");
        added += body.added;
        skipped += body.skipped;
      }
      return { added, skipped };
    },
    onSuccess: (totals) => {
      setBasket({});
      setDone(totals);
      // Added properties change the search's alreadyInPortfolio flags
      queryClient.invalidateQueries(["postcode-search", portfolioId]);
      // …and the portfolio's property table. Remove, don't invalidate: the
      // table isn't mounted here, and useProperties sets refetchOnMount:
      // false, so a stale-marked query would never actually refetch — only
      // an empty cache guarantees a fresh fetch when the table mounts.
      queryClient.removeQueries(["properties", portfolioId]);
      router.refresh();
    },
  });

  const doSearch = () => {
    const pc = normalisePostcode(input);
    if (!pc) {
      setInputError("That doesn't look like a full UK postcode");
      return;
    }
    setInputError(null);
    setDone(null);
    setFilter("");
    setSearch({ pc, refresh: 0 });
  };

  const toggle = (postcode: string, geo: PostcodeGeo, c: SearchCandidate) => {
    setBasket((prev) => {
      const group = prev[postcode] ?? { geo, byUprn: {} };
      const byUprn = { ...group.byUprn };
      if (byUprn[c.uprn]) delete byUprn[c.uprn];
      else byUprn[c.uprn] = c;
      const next = { ...prev, [postcode]: { geo, byUprn } };
      if (Object.keys(byUprn).length === 0) delete next[postcode];
      return next;
    });
  };

  const data = done ? undefined : query.data;
  const selected = data ? (basket[data.postcode]?.byUprn ?? {}) : {};
  const addable = data
    ? data.candidates.filter((c) => c.selectable && !c.alreadyInPortfolio)
    : [];
  const needle = filter.trim().toLowerCase();
  const visible = data
    ? needle
      ? data.candidates.filter((c) => c.address.toLowerCase().includes(needle))
      : data.candidates
    : [];
  const totalSelected = countSelected(basket);
  const postcodes = Object.keys(basket);

  return (
    <div className="max-w-3xl mx-auto px-6 pt-10 pb-40">
      {/* Breadcrumb */}
      <div className="text-xs text-slate-400 mb-4">
        <Link href={`/portfolio/${portfolioId}`} className="hover:text-primary">
          {portfolioName} / Portfolio
        </Link>{" "}
        / Add properties
      </div>

      <h1 className="text-3xl font-bold text-gray-800 tracking-tight mb-3">
        Bring your properties in.
      </h1>
      <p className="text-sm text-slate-500 max-w-xl leading-relaxed">
        Search a postcode and pick the homes that belong to this portfolio. We
        identify each address against the national register — type and built
        form come with it, ready for modelling.
      </p>

      {/* Search card */}
      <div className="mt-8 bg-white border border-slate-200 rounded-2xl p-5 shadow-sm">
        <span className="block text-[11px] font-black uppercase tracking-wider text-primary mb-3">
          Search by postcode
        </span>
        <div className="flex gap-2">
          <input
            value={input}
            onChange={(e) => setInput(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === "Enter") doSearch();
            }}
            placeholder='e.g. "M20 4TF"'
            autoFocus
            spellCheck={false}
            autoComplete="off"
            autoCorrect="off"
            className="flex-1 border border-slate-200 rounded-xl px-4 py-2.5 text-base font-semibold uppercase placeholder:normal-case placeholder:font-normal placeholder:text-sm placeholder:text-slate-400 focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/10"
          />
          <button
            onClick={doSearch}
            disabled={query.isFetching}
            className="flex items-center gap-1.5 px-5 rounded-xl bg-primary text-white text-sm font-semibold hover:opacity-90 transition disabled:opacity-50"
          >
            <MagnifyingGlassIcon className="h-4 w-4" />
            {query.isFetching ? "Searching…" : "Search"}
          </button>
        </div>
        <div className="mt-3 text-xs text-slate-400">
          {inputError ? (
            <span className="text-red-600">{inputError}</span>
          ) : query.isError ? (
            <span className="text-red-600">{query.error.message}</span>
          ) : data ? (
            data.source === "cache" ? (
              <>
                Results from our cache,{" "}
                <b className="text-slate-600 font-semibold">
                  {data.cacheAgeDays === 0
                    ? "refreshed today"
                    : `${data.cacheAgeDays} day${data.cacheAgeDays === 1 ? "" : "s"} old`}
                </b>{" "}
                ·{" "}
                <button
                  onClick={() =>
                    setSearch((s) => s && { ...s, refresh: s.refresh + 1 })
                  }
                  className="text-primary font-semibold hover:underline"
                >
                  Refresh from source
                </button>
              </>
            ) : (
              <>Fresh from the national register just now</>
            )
          ) : (
            <>We look every postcode up once and remember it — repeat searches are instant.</>
          )}
        </div>
      </div>

      {/* Done state */}
      {done && (
        <div className="mt-6 bg-white border border-slate-200 rounded-2xl px-8 py-12 text-center shadow-sm">
          <div className="inline-flex items-center justify-center w-14 h-14 rounded-full bg-amber-100 text-amber-800 mb-4">
            <CheckIcon className="h-7 w-7" />
          </div>
          <h2 className="text-2xl font-bold text-gray-800 mb-2">
            {done.added} {done.added === 1 ? "property" : "properties"} added
          </h2>
          <p className="text-sm text-slate-500 max-w-md mx-auto leading-relaxed mb-2">
            They&apos;re in the portfolio now, marked{" "}
            <span className="inline-flex items-center gap-1.5 bg-amber-100 text-amber-800 rounded-full px-2.5 py-0.5 text-xs font-semibold">
              <span className="w-1.5 h-1.5 rounded-full bg-amber-800" />
              Awaiting modelling
            </span>{" "}
            — addresses are real from day one; energy data arrives when you run
            a scenario against them.
          </p>
          {done.skipped > 0 && (
            <p className="text-xs text-slate-400 mb-2">
              {done.skipped} {done.skipped === 1 ? "was" : "were"} already in
              the portfolio and left untouched.
            </p>
          )}
          <div className="mt-5 flex items-center justify-center gap-3">
            <button
              onClick={() => setDone(null)}
              className="px-4 py-2 rounded-xl border border-slate-200 bg-white text-sm font-semibold text-primary hover:bg-slate-50 transition"
            >
              Add more properties
            </button>
            <button
              onClick={() => router.push(`/portfolio/${portfolioId}`)}
              className="px-4 py-2 rounded-xl bg-primary text-white text-sm font-semibold hover:opacity-90 transition"
            >
              View portfolio →
            </button>
          </div>
        </div>
      )}

      {/* Results */}
      {!done && !data && !query.isFetching && (
        <div className="mt-6 border-2 border-dashed border-slate-200 rounded-2xl py-10 text-center text-sm text-slate-400">
          Searched addresses appear here — tick the ones that belong to this
          portfolio.
        </div>
      )}

      {!done && data && (
        <div className="mt-6 bg-white border border-slate-200 rounded-2xl p-5 shadow-sm">
          <div className="flex items-baseline gap-3 flex-wrap mb-4">
            <h2 className="text-lg font-bold text-gray-800">{data.postcode}</h2>
            <span className="text-xs text-slate-400 font-medium">
              {needle
                ? `${visible.length} of ${data.candidates.length}`
                : data.candidates.length}{" "}
              {data.candidates.length === 1 ? "address" : "addresses"}
            </span>
            {addable.length > 0 && (
              <>
                <button
                  onClick={() =>
                    setBasket((prev) => ({
                      ...prev,
                      [data.postcode]: {
                        geo: data.geo,
                        byUprn: Object.fromEntries(
                          addable.map((c) => [c.uprn, c]),
                        ),
                      },
                    }))
                  }
                  className="text-xs font-semibold text-primary hover:underline"
                >
                  Add all
                </button>
                <button
                  onClick={() =>
                    setBasket((prev) => {
                      const next = { ...prev };
                      delete next[data.postcode];
                      return next;
                    })
                  }
                  className="text-xs font-semibold text-primary hover:underline"
                >
                  Clear
                </button>
              </>
            )}
            <span className="ml-auto text-xs text-slate-500 tabular-nums">
              {Object.keys(selected).length} selected
            </span>
          </div>

          {/* Long postcodes can return ~100 addresses — a filter lets the user
              jump to a house number or street name without scrolling. */}
          {data.candidates.length > 8 && (
            <div className="relative mb-3">
              <MagnifyingGlassIcon className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-slate-400 pointer-events-none" />
              <input
                value={filter}
                onChange={(e) => setFilter(e.target.value)}
                placeholder="Filter these addresses…"
                spellCheck={false}
                autoComplete="off"
                className="w-full border border-slate-200 rounded-xl pl-9 pr-9 py-2 text-sm focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/10"
              />
              {filter && (
                <button
                  aria-label="Clear filter"
                  onClick={() => setFilter("")}
                  className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm leading-none hover:text-slate-700"
                >
                  ✕
                </button>
              )}
            </div>
          )}

          {/* Long postcodes can return ~100 addresses — scroll inside the card
              rather than growing the page. */}
          <div className="max-h-[26rem] overflow-y-auto pr-1 -mr-1">
          {visible.length === 0 ? (
            <p className="py-8 text-center text-sm text-slate-400">
              No addresses match &ldquo;{filter.trim()}&rdquo;.
            </p>
          ) : (
          visible.map((c) => {
            const { label, note } = describeCandidate(c);
            const disabled = !c.selectable || c.alreadyInPortfolio;
            const isSelected = !!selected[c.uprn];
            return (
              <button
                key={c.uprn}
                disabled={disabled}
                onClick={() => toggle(data.postcode, data.geo, c)}
                className={[
                  "w-full flex items-center gap-3 text-left px-4 py-3 rounded-xl border mb-1.5 transition",
                  disabled
                    ? "opacity-50 cursor-not-allowed bg-slate-50 border-transparent"
                    : isSelected
                      ? "bg-white border-primary shadow-sm"
                      : "bg-slate-50 border-transparent hover:bg-slate-100",
                ].join(" ")}
              >
                <input
                  type="checkbox"
                  checked={isSelected}
                  disabled={disabled}
                  readOnly
                  tabIndex={-1}
                  className="h-4 w-4 accent-black pointer-events-none shrink-0"
                />
                <span className="flex-1 min-w-0">
                  <span className="block text-sm font-medium text-gray-800 truncate">
                    {c.address}
                  </span>
                  {note && !disabled && (
                    <span className="block text-xs text-slate-400">{note}</span>
                  )}
                </span>
                {disabled && (
                  <span className="text-[11px] text-slate-400 font-medium shrink-0">
                    {c.alreadyInPortfolio ? "Already in portfolio" : "Not residential"}
                  </span>
                )}
                <span
                  className={[
                    "text-[10px] font-bold uppercase tracking-wide rounded-full px-2.5 py-1 shrink-0",
                    disabled
                      ? "bg-slate-200 text-slate-500"
                      : "bg-amber-100 text-amber-900",
                  ].join(" ")}
                >
                  {label}
                </span>
              </button>
            );
          })
          )}
          </div>
        </div>
      )}

      {/* Selection tray */}
      {totalSelected > 0 && !done && (
        <div className="fixed bottom-6 left-1/2 -translate-x-1/2 z-40 w-[min(56rem,calc(100vw-2rem))] bg-white/95 backdrop-blur border border-slate-200 rounded-2xl shadow-2xl px-4 py-3 flex items-center gap-3 flex-wrap">
          {/* Many postcodes: chips scroll inside the tray instead of growing it */}
          <div className="flex items-center gap-2 flex-wrap max-h-16 overflow-y-auto">
          {postcodes.map((pc) => (
            <span
              key={pc}
              className="inline-flex items-center gap-2 bg-slate-100 rounded-full pl-3 pr-1.5 py-1 text-xs font-semibold text-gray-700"
            >
              {pc} · {Object.keys(basket[pc].byUprn).length}
              <button
                aria-label={`Remove ${pc}`}
                onClick={() =>
                  setBasket((prev) => {
                    const next = { ...prev };
                    delete next[pc];
                    return next;
                  })
                }
                className="w-[18px] h-[18px] rounded-full bg-white text-slate-500 text-[10px] leading-none px-1 py-0.5 hover:text-slate-800"
              >
                ✕
              </button>
            </span>
          ))}
          </div>
          <span className="text-xs text-slate-500">
            <b className="text-gray-800">{totalSelected}</b>{" "}
            {totalSelected === 1 ? "property" : "properties"} across{" "}
            {postcodes.length} {postcodes.length === 1 ? "postcode" : "postcodes"}
          </span>
          {submit.isError && (
            <span className="text-xs text-red-600">{submit.error.message}</span>
          )}
          <button
            onClick={() => submit.mutate(basket)}
            disabled={submit.isLoading}
            className="ml-auto px-4 py-2 rounded-xl bg-primary text-white text-sm font-semibold hover:opacity-90 transition disabled:opacity-50"
          >
            {submit.isLoading
              ? "Adding…"
              : `Add ${totalSelected} ${totalSelected === 1 ? "property" : "properties"}`}
          </button>
        </div>
      )}
    </div>
  );
}
