/**
 * Reonomy Scraper Service
 *
 * Bridges the Puppeteer-based Reonomy scraper (v13) with the LocalBosses
 * Next.js API. Manages background scrape jobs, reads real results, and
 * tracks daily limits.
 *
 * The scraper runs as a child process using agent-browser to control a
 * real browser instance on the Mac mini.
 */

import { exec, ChildProcess } from "child_process";
import { readFileSync, existsSync, writeFileSync } from "fs";

// ─── Paths (computed at runtime to avoid Turbopack static analysis) ──────────
// Turbopack traces join() calls and tries to resolve them at build time.
// Use string concatenation + path.resolve at runtime to keep it opaque.

function getWorkspace(): string {
  const home = process.env.HOME || "/Users/jakeshore";
  // eslint-disable-next-line prefer-template
  return home + "/.clawdbot/workspace";
}

function getPaths() {
  const ws = getWorkspace();
  return {
    scraper: ws + "/reonomy-scraper-v13.js",
    leads: ws + "/reonomy-leads-v13.json",
    dailyStats: ws + "/reonomy-daily-stats.json",
    authState: ws + "/reonomy-auth.json",
    log: ws + "/reonomy-scraper-v13.log",
    savedSearches: ws + "/reonomy-saved-searches.json",
  };
}

// ─── Types ───────────────────────────────────────────────────────────────────
export interface ReonomyOwner {
  name: string;
  phones: { number: string; type: string }[];
  emails: string[];
}

export interface ReonomyLead {
  scrapeDate: string;
  propertyId: string;
  propertyAddress: string;
  propertyType?: string;
  squareFootage?: string;
  yearBuilt?: string;
  lotSize?: string;
  units?: string;
  city?: string;
  state?: string;
  zip?: string;
  owners: ReonomyOwner[];
}

export interface ReonomyLeadsFile {
  lastUpdated: string;
  searchId: string;
  totalLeads: number;
  leads: ReonomyLead[];
}

export interface DailyStats {
  date: string;
  propertiesScraped: number;
  leadsFound: number;
}

export interface ScrapeJob {
  id: string;
  status: "running" | "completed" | "failed";
  searchId: string;
  maxProperties: number;
  startedAt: string;
  completedAt?: string;
  leadsBeforeCount: number;
  leadsAfterCount?: number;
  newLeads?: number;
  error?: string;
  logTail?: string;
  process?: ChildProcess;
}

// ─── Job Store (in-memory) ───────────────────────────────────────────────────
const jobs = new Map<string, ScrapeJob>();

// ─── Read helpers ────────────────────────────────────────────────────────────

export function readLeads(): ReonomyLeadsFile | null {
  const p = getPaths();
  if (!existsSync(p.leads)) return null;
  try {
    return JSON.parse(readFileSync(p.leads, "utf-8")) as ReonomyLeadsFile;
  } catch {
    return null;
  }
}

export function readDailyStats(): DailyStats {
  const p = getPaths();
  const today = new Date().toISOString().split("T")[0];
  if (!existsSync(p.dailyStats)) {
    return { date: today, propertiesScraped: 0, leadsFound: 0 };
  }
  try {
    const data = JSON.parse(readFileSync(p.dailyStats, "utf-8")) as DailyStats;
    if (data.date === today) return data;
    return { date: today, propertiesScraped: 0, leadsFound: 0 };
  } catch {
    return { date: today, propertiesScraped: 0, leadsFound: 0 };
  }
}

export function readLogTail(lines = 30): string {
  const p = getPaths();
  if (!existsSync(p.log)) return "";
  try {
    const content = readFileSync(p.log, "utf-8");
    const allLines = content.split("\n");
    return allLines.slice(-lines).join("\n");
  } catch {
    return "";
  }
}

// ─── Scrape Job Management ──────────────────────────────────────────────────

export function startScrapeJob(opts: {
  searchId?: string;
  maxProperties?: number;
  location?: string;
}): ScrapeJob {
  const id = `scrape-${Date.now()}`;
  const searchId = opts.searchId || "bacfd104-fed5-4cc4-aba1-933f899de3f8";
  const maxProperties = opts.maxProperties || 10;

  // Check daily limits first
  const stats = readDailyStats();
  if (stats.propertiesScraped >= 50) {
    const failedJob: ScrapeJob = {
      id,
      status: "failed",
      searchId,
      maxProperties,
      startedAt: new Date().toISOString(),
      completedAt: new Date().toISOString(),
      leadsBeforeCount: readLeads()?.totalLeads || 0,
      error: `Daily limit reached (${stats.propertiesScraped}/50). Try again tomorrow.`,
    };
    jobs.set(id, failedJob);
    return failedJob;
  }

  const currentLeads = readLeads();
  const leadsBeforeCount = currentLeads?.totalLeads || 0;

  // Run the scraper as a shell command (exec avoids Turbopack tracing file paths)
  const ws = getWorkspace();
  const scraperPath = ws + "/reonomy-scraper-v13.js";
  const cmd = [
    `REONOMY_SEARCH_ID="${searchId}"`,
    `MAX_PROPERTIES="${maxProperties}"`,
    `HEADLESS="true"`,
    `node "${scraperPath}"`,
  ].join(" ");

  const job: ScrapeJob = {
    id,
    status: "running",
    searchId,
    maxProperties,
    startedAt: new Date().toISOString(),
    leadsBeforeCount,
    process: undefined as unknown as ChildProcess,
  };

  const child = exec(cmd, { cwd: ws, maxBuffer: 10 * 1024 * 1024 }, (error, _stdout, stderr) => {
    const updatedLeads = readLeads();
    const leadsAfterCount = updatedLeads?.totalLeads || 0;

    job.completedAt = new Date().toISOString();
    job.leadsAfterCount = leadsAfterCount;
    job.newLeads = leadsAfterCount - leadsBeforeCount;
    job.logTail = readLogTail(20);
    delete job.process;

    if (!error) {
      job.status = "completed";
    } else {
      job.status = "failed";
      job.error = (stderr || "").slice(-500) || error.message;
    }
  });

  job.process = child;

  jobs.set(id, job);
  return job;
}

export function getJob(id: string): Omit<ScrapeJob, "process"> | null {
  const job = jobs.get(id);
  if (!job) return null;

  // If job is still running, check if scraper made progress
  if (job.status === "running") {
    const updatedLeads = readLeads();
    const currentLeadCount = updatedLeads?.totalLeads || 0;
    return {
      ...job,
      leadsAfterCount: currentLeadCount,
      newLeads: currentLeadCount - job.leadsBeforeCount,
      logTail: readLogTail(10),
    };
  }

  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const { process: _, ...rest } = job;
  return rest;
}

export function listJobs(): Omit<ScrapeJob, "process">[] {
  return Array.from(jobs.values()).map((j) => {
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const { process: _, ...rest } = j;
    return rest;
  });
}

// ─── Search management ──────────────────────────────────────────────────────
// Reonomy searches are identified by search IDs from the URL
// Users can create searches on Reonomy's UI and we store the IDs

interface SavedSearch {
  id: string;
  name: string;
  searchId: string;
  description?: string;
  createdAt: string;
}

export function getSavedSearches(): SavedSearch[] {
  const p = getPaths();
  if (!existsSync(p.savedSearches)) return getDefaultSearches();
  try {
    return JSON.parse(readFileSync(p.savedSearches, "utf-8")) as SavedSearch[];
  } catch {
    return getDefaultSearches();
  }
}

function getDefaultSearches(): SavedSearch[] {
  return [
    {
      id: "default-fl-multi",
      name: "FL Multifamily with Phone",
      searchId: "bacfd104-fed5-4cc4-aba1-933f899de3f8",
      description: "Florida multifamily properties with owner phone numbers available",
      createdAt: "2026-01-26T00:00:00.000Z",
    },
    {
      id: "default-nj",
      name: "NJ Commercial Properties",
      searchId: "504a2d13-d88f-4213-9ac6-a7c8bc7c20c6",
      description: "New Jersey commercial properties (mixed types)",
      createdAt: "2026-01-15T00:00:00.000Z",
    },
  ];
}

export function saveSearch(search: SavedSearch): void {
  const p = getPaths();
  const searches = getSavedSearches();
  const idx = searches.findIndex((s) => s.id === search.id);
  if (idx >= 0) searches[idx] = search;
  else searches.push(search);
  writeFileSync(p.savedSearches, JSON.stringify(searches, null, 2));
}

// ─── Export helpers ─────────────────────────────────────────────────────────

export function exportLeadsAsCsv(leads: ReonomyLead[]): string {
  const headers = [
    "Property Address",
    "City",
    "State",
    "ZIP",
    "Property Type",
    "Square Footage",
    "Year Built",
    "Units",
    "Owner Name",
    "Phone Numbers",
    "Emails",
    "Scrape Date",
  ];

  const rows = leads.flatMap((lead) =>
    lead.owners.map((owner) => [
      lead.propertyAddress,
      lead.city || "",
      lead.state || "",
      lead.zip || "",
      lead.propertyType || "",
      lead.squareFootage || "",
      lead.yearBuilt || "",
      lead.units || "",
      owner.name,
      owner.phones.map((p) => p.number).join("; "),
      owner.emails.join("; "),
      lead.scrapeDate,
    ])
  );

  const csvEscape = (val: string) => {
    if (val.includes(",") || val.includes('"') || val.includes("\n")) {
      return `"${val.replace(/"/g, '""')}"`;
    }
    return val;
  };

  return [
    headers.join(","),
    ...rows.map((row) => row.map(csvEscape).join(",")),
  ].join("\n");
}

export function exportLeadsAsJson(leads: ReonomyLead[]): string {
  return JSON.stringify({ exportDate: new Date().toISOString(), totalLeads: leads.length, leads }, null, 2);
}
