const Anthropic = require('@anthropic-ai/sdk/index.js');
const { MOCK_MATCHES, MOCK_PROFILE } = require('./apify');

const MOCK_MODE = () => process.env.MOCK_MODE !== 'false' && process.env.MOCK_MODE !== '0';
const MODEL_HAIKU = 'claude-haiku-4-5-20251001';
const MODEL_SONNET = 'claude-sonnet-4-6';

function client() {
  const key = process.env.ANTHROPIC_API_KEY;
  if (!key) throw new Error('ANTHROPIC_API_KEY not configured');
  return new Anthropic({ apiKey: key });
} 

function sanitizeJsonStrings(raw) {
  // Escape literal newlines/tabs inside JSON string values (a common model output issue)
  return raw.replace(/"((?:[^"\\]|\\.)*)"/g, (_, inner) =>
    '"' + inner.replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\t/g, '\\t') + '"'
  );
}

function extractJson(text) {
  text = text.trim();
  const fence = text.match(/```(?:json)?\s*([\s\S]+?)```/);
  if (fence) text = fence[1].trim();
  try { return JSON.parse(text); } catch {}
  const m = text.match(/\{[\s\S]*\}/);
  if (!m) {
    console.error('[claude] extractJson: no JSON object found in response (first 300 chars):', text.slice(0, 300));
    throw new Error('No JSON found in response');
  }
  try { return JSON.parse(m[0]); } catch {}
  try { return JSON.parse(sanitizeJsonStrings(m[0])); } catch {}
  console.error('[claude] extractJson: found JSON-like block but failed to parse (first 300 chars):', m[0].slice(0, 300));
  throw new Error('No JSON found in response');
}

const EXTRACT_SYSTEM = `You extract structured networking attributes from a LinkedIn profile.
Return strictly this JSON shape (no prose):
{
  "schools": [string],
  "locations": [string],
  "companies": [string],
  "industries": [string],
  "languages": [string],
  "skills": [string]
}
- Deduplicate entries.
- Normalize city names (e.g., "SF Bay Area" -> "San Francisco").
- Leave arrays empty if unknown. Never guess.`;

const SCORE_SYSTEM = `You rank people at a target company by how likely they are to (a) accept a
LinkedIn connection from the applicant and (b) be helpful in the applicant's
job search. Prioritize: direct_connection > shared work history > shared school
> shared location > shared industry > shared language > shared skills.

Return strictly this JSON shape (no prose):
{
  "matches": [
    {
      "name": string,
      "title": string,
      "linkedin_url": string,
      "location": string | null,
      "score": int 1-100,
      "connection_type": "direct_connection" | "strong_match" | "moderate_match",
      "match_reasons": [
        {"type": "direct_connection"|"school"|"company"|"location"|"industry"|"language"|"skill"|"role",
         "detail": string}
      ],
      "approach_angle": string,
      "draft_message": string
    }
  ]
}

Rules:
- Output 3-10 people total, ranked best-first.
- A candidate with is_direct_connection: true MUST get connection_type "direct_connection" and score >=90.
- Keep match_reasons concrete ("Both attended DTU"), not generic ("Shared background").
- approach_angle: one sentence of internal strategy.

draft_message — the actual LinkedIn connection note. HARD RULES:
  1. TARGET 160-180 characters. ABSOLUTE MAX 200 including spaces.
  2. Written in FIRST PERSON from the applicant to the recipient.
  3. Start with "Hi <first name>," or "Hi <first name> —".
  4. Must reference at least ONE specific detail from the applicant's background.
  5. Must reference at least ONE specific detail tied to the recipient's role.
  6. Must explicitly ASK FOR ADVICE. Not a job, not a referral.
  7. Direct. No filler. No adjectives for their work.
  8. MUST end with a short, concrete question ("Open to a quick chat?", "Any advice?", "Worth 15 min?").

Good example (152 chars):
"Hi Adrian — YC founder (LatAm fintech SaaS) targeting Google. You lead channel partners for SMBs — the exact GTM layer I'm trying to learn. Any advice?"`;

async function extractProfileAttributes(profileJson) {
  if (MOCK_MODE()) {
    const p = MOCK_PROFILE;
    return { schools: extractNames(p.schools, 'schoolName', 'name'), locations: [p.locationName || ''], companies: extractNames(p.companies, 'companyName', 'name'), industries: extractNames(p.industries, 'name'), languages: extractNames(p.languages, 'name'), skills: p.skills || [] };
  }
  const c = client();
  const resp = await c.messages.create({
    model: MODEL_HAIKU, max_tokens: 1024,
    system: [{ type: 'text', text: EXTRACT_SYSTEM, cache_control: { type: 'ephemeral' } }],
    messages: [{ role: 'user', content: 'Profile JSON:\n' + JSON.stringify(profileJson, null, 2) }],
  });
  return extractJson(resp.content[0].text);
}

const MAX_CHARS = 200;
const SOFT_TARGET = 180;

function clampMessage(text) {
  if (!text || typeof text !== 'string') return '';
  text = text.trim();
  if (text.length <= MAX_CHARS) return text;
  let trimmed = text.slice(0, MAX_CHARS - 1);
  const lastSpace = trimmed.lastIndexOf(' ');
  if (lastSpace > (MAX_CHARS - 1) * 0.6) trimmed = trimmed.slice(0, lastSpace);
  return trimmed.replace(/[,.;:— ]+$/, '') + '…';
}

async function rewriteOverlong(c, matches) {
  const overlong = matches.map((m, i) => [i, m]).filter(([, m]) => typeof m.draft_message === 'string' && m.draft_message.length > SOFT_TARGET);
  if (!overlong.length) return matches;
  const items = overlong.map(([i, m]) => ({ idx: i, name: m.name || '', message: m.draft_message }));
  const instruction = `Rewrite each LinkedIn connection message below to fit in 170 characters or fewer. HARD RULES per message: Start with 'Hi <first name> —', keep most specific applicant+recipient details, keep closing question. Return STRICT JSON: {"rewrites": [{"idx": <int>, "message": <string>}, ...]}. No prose outside JSON.\n\nInput:\n${JSON.stringify({ messages: items }, null, 2)}`;
  try {
    const resp = await c.messages.create({ model: MODEL_HAIKU, max_tokens: 1024, messages: [{ role: 'user', content: instruction }] });
    const parsed = extractJson(resp.content[0].text);
    for (const r of parsed.rewrites || []) {
      if (typeof r.idx === 'number' && matches[r.idx] && r.message) matches[r.idx].draft_message = r.message.trim();
    }
  } catch {}
  return matches;
}

async function scoreAndRank(applicantAttrs, candidates, targetCompany) {
  if (MOCK_MODE()) return MOCK_MATCHES;
  const c = client();
  const payload = { applicant: applicantAttrs, target_company: targetCompany, candidates };
  const resp = await c.messages.create({
    model: MODEL_SONNET, max_tokens: 4096,
    system: [{ type: 'text', text: SCORE_SYSTEM, cache_control: { type: 'ephemeral' } }],
    messages: [{ role: 'user', content: 'Data:\n' + JSON.stringify(payload, null, 2) }],
  });
  const parsed = extractJson(resp.content[0].text);
  let matches = parsed.matches || [];
  matches = matches.map((m, i) => ({ ...m, rank: i + 1, draft_message: (m.draft_message || '').trim() }));
  matches = await rewriteOverlong(c, matches);
  matches = matches.map(m => ({ ...m, draft_message: clampMessage(m.draft_message) }));
  return matches;
}

function extractNames(items, ...keys) {
  return (items || []).flatMap(it => {
    if (typeof it === 'string' && it.trim()) return [it.trim()];
    if (typeof it === 'object' && it) { for (const k of keys) { if (it[k]) return [it[k]]; } }
    return [];
  });
}

module.exports = { extractProfileAttributes, scoreAndRank };
