/**
 * `/stocks judge` 의 결정론적 기준 평가기.
 *
 * 기존에는 "유보율: 5,800%" 같은 문자열 파싱과 임계값 비교(ROE ≥ 10% 등)까지
 * 전부 LLM 에게 맡겼는데, 로컬 소형 모델은 콤마 숫자 파싱·다중 기준 동시 비교에서
 * 자주 틀린다. 수치 기준은 코드로 100% 정확하게 계산하고, LLM 은 ① '기술력'
 * 도메인 정성 판단(키워드 매칭이 모호할 때만) ② 근거 문장 서술만 담당한다.
 * 충족/미충족 판정과 대표 키워드 선택은 사용자가 명시한 규칙(투자성향별
 * 우선순위)을 그대로 코드화했다.
 *
 * v2.2.212 정밀화 (퀀트 실증 근거 반영):
 *  - ROE 에 레버리지 가드(부채비율 ≤150%) — 빚으로 부풀린 ROE 배제 (듀폰분해).
 *  - 성장성을 실측 YoY(매출 ≥10% / 영업이익 ≥15%) 1순위로 — 마진 수준은 성장이 아님.
 *  - 안정성에 부채비율 ≤100% 가드 — 유보율은 자본금 크기에 왜곡되는 약한 지표.
 *  - PER ≤12배 키워드 신설 — 보유 데이터인데 미사용이던 가격 매력(이익수익률) 축.
 *
 * signalClassifier 의 `.includes("충족")` 계약과 "충족 (A, B, C)" 출력 형식은
 * 기존 그대로 유지된다.
 */
import type { Stock } from './types';
import type { Fundamentals } from './naverFundamentals';
import { detectMarket, type StockMarket } from './marketUtils';

export type CriterionState = 'pass' | 'fail' | 'unknown' | 'llm';
export interface CriterionResult {
    keyword: string;
    state: CriterionState;        // unknown = 데이터 없음, llm = 정성 판단 필요(기술력 도메인)
    detail: string;               // 수치 근거 한 줄 (rationale 합성·로그용)
    /** 대표 키워드 표기 시 사용할 라벨 (예: 영업이익률 ≥ 20% 이면 '수익성 개선'). */
    label?: string;
}
export interface CriteriaEvaluation {
    results: CriterionResult[];
    /** 데이터 출처 표기 ('Naver 실시간 YYYY-MM-DD' | 'stocks.json 저장값'). */
    dataSource: string;
    /** 파싱된 수치 (LLM 프롬프트·rationale 에 인용). */
    numbers: Record<string, string>;
}

// ── 문자열 → 숫자 파싱 (stocks.json 의 한글 포맷 대응) ──────────────────────
function num(raw: string | number | undefined): number | undefined {
    if (raw === undefined || raw === null) return undefined;
    if (typeof raw === 'number') return Number.isFinite(raw) ? raw : undefined;
    const cleaned = String(raw).replace(/[,%\s원]/g, '');
    if (!cleaned || cleaned === '-') return undefined;
    const n = parseFloat(cleaned);
    return Number.isFinite(n) ? n : undefined;
}

/** "1조 2,000억" / "5,000억" / "5000" → 억 단위 숫자. */
export function marketCapEok(raw: string | undefined): number | undefined {
    if (!raw) return undefined;
    const s = String(raw).replace(/\s/g, '');
    const jo = s.match(/([\d,.]+)조/);
    const eok = s.match(/(?:조)?([\d,]+)억/);
    if (jo || eok) {
        const j = jo ? parseFloat(jo[1].replace(/,/g, '')) : 0;
        const e = eok ? parseInt(eok[1].replace(/,/g, ''), 10) : 0;
        const total = j * 10000 + e;
        return total > 0 ? total : undefined;
    }
    return num(s);
}

/** 상장일 'YYYY-MM-DD' → 상장 후 경과 연수. 파싱 불가면 undefined. */
function yearsSinceListing(listed: string | undefined, now: Date): number | undefined {
    if (!listed) return undefined;
    const d = new Date(listed);
    if (Number.isNaN(d.getTime())) return undefined;
    return (now.getTime() - d.getTime()) / (365.25 * 24 * 3600 * 1000);
}

// '기술력' 도메인 키워드 — 명백히 기술 영역이면 LLM 호출 없이 통과.
const TECH_KEYWORDS = /ai|인공지능|반도체|배터리|2차전지|이차전지|바이오|로봇|소프트웨어|플랫폼|클라우드|데이터|센서|팹리스|디스플레이|통신장비|자율주행|드론|우주|방산레이더|보안솔루션/i;

const fmt = (v: number | undefined, suffix = '%') => (v === undefined ? '-' : `${v.toLocaleString()}${suffix}`);

/**
 * 8개 기준 평가. fresh(나버 실시간)가 있으면 그 수치를 우선 사용하고,
 * 없으면 stocks.json 의 저장 문자열을 파싱한다.
 */
export function evaluateCriteria(stock: Stock, fresh?: Fundamentals, now: Date = new Date()): CriteriaEvaluation {
    const roe = fresh?.roe ?? num(stock['ROE(25E)']);
    const opm = fresh?.operatingMargin ?? num(stock['영업이익률(25E)']);
    const ret = fresh?.retentionRatio ?? num(stock.유보율);
    const pbr = fresh?.pbr ?? num(stock.PBR);
    const per = fresh?.per ?? num(stock['PER(25E)']);
    const debt = fresh?.debtRatio;                 // 부채비율 — fresh 전용 (stocks.json 미보유)
    const revYoY = fresh?.revenueGrowthYoY;        // 매출 YoY % — fresh 전용
    const opYoY = fresh?.opProfitGrowthYoY;        // 영업이익 YoY % — fresh 전용
    const cap = fresh?.marketCapEok ?? marketCapEok(stock.시가총액);
    const listedYears = yearsSinceListing(stock.상장일, now);
    const biz = (stock['최대 먹거리'] || '').trim();
    // 시장 판별 — 미국 종목은 유보율(retentionRatio)을 보고하지 않으므로 '유동성'·'안정성'
    // 기준을 부채비율(D/E)·시가총액(native)으로 대체한다. ROE/성장성/수익성/PBR/PER 등
    // 나머지 6개 기준은 시장 무관 보편 지표라 그대로 공유.
    const market: StockMarket = fresh?.market ?? detectMarket(stock.심볼);
    const capNative = fresh?.marketCapNative;     // US=USD 시총

    const R = (keyword: string, cond: boolean | undefined, detail: string, label?: string): CriterionResult =>
        ({ keyword, state: cond === undefined ? 'unknown' : cond ? 'pass' : 'fail', detail, label });

    const results: CriterionResult[] = [];

    // ROE — 듀폰분해상 레버리지로 부풀린 ROE 를 거른다: 부채비율(있으면) ≤150% 동반 요구.
    const roeBase = roe === undefined ? undefined : roe >= 10;
    const leverageOk = debt === undefined ? true : debt <= 150;
    results.push(R('ROE', roeBase === undefined ? undefined : (roeBase && leverageOk),
        `ROE ${fmt(roe)} (기준 ≥10%${roe !== undefined && roe >= 15 ? ', 15% 이상 우수' : ''})`
        + (debt !== undefined ? ` · 부채비율 ${fmt(debt)} (레버리지 가드 ≤150%)` : '')));

    // 성장성 — 실측 YoY(매출 ≥10% 또는 영업이익 ≥15%)를 1순위로. YoY 미확보 시에만
    // 기존 대용물(마진 수준·상장 연차)로 폴백 — 마진 15%는 '수익성 수준'이지 성장이 아님.
    let growth: boolean | undefined;
    let growthDetail: string;
    if (revYoY !== undefined || opYoY !== undefined) {
        growth = (revYoY !== undefined && revYoY >= 10) || (opYoY !== undefined && opYoY >= 15);
        growthDetail = `매출 YoY ${fmt(revYoY)} (기준 ≥10%) 또는 영업이익 YoY ${fmt(opYoY)} (기준 ≥15%) [실측]`;
    } else {
        const growthByMargin = opm === undefined ? undefined : opm >= 15;
        const growthByListing = listedYears === undefined ? undefined : listedYears <= 3;
        growth = growthByMargin === true || growthByListing === true ? true
            : growthByMargin === undefined && growthByListing === undefined ? undefined : false;
        growthDetail = `영업이익률 ${fmt(opm)} (기준 ≥15%) 또는 상장 ${listedYears === undefined ? '미상' : listedYears.toFixed(1) + '년'} (기준 ≤3년) [YoY 미확보 폴백]`;
    }
    results.push(R('성장성', growth, growthDetail));

    if (market === 'US') {
        // 미국: 유보율 미보고 → 부채비율(D/E) ≤100% 를 '재무여력' 대체 지표로.
        results.push(R('유동성', debt === undefined ? undefined : debt <= 100,
            `부채비율(D/E) ${fmt(debt)} (기준 ≤100% · 미국은 유보율 미보고 → 재무여력 대체)`));
    } else {
        results.push(R('유동성', ret === undefined ? undefined : ret >= 1000,
            `유보율 ${fmt(ret)} (기준 ≥1,000%)`));
    }

    const profitImproved = opm !== undefined && opm >= 20;
    results.push(R('수익성', opm === undefined ? undefined : opm >= 10,
        `영업이익률 ${fmt(opm)} (기준 ≥10%${profitImproved ? ', 20% 이상 → 수익성 개선' : ''})`,
        profitImproved ? '수익성 개선' : undefined));

    const eff = opm === undefined || roe === undefined ? undefined : (opm >= 15 && roe >= 8);
    results.push(R('영업효율', eff, `영업이익률 ${fmt(opm)} ≥15% AND ROE ${fmt(roe)} ≥8%`));

    // 기술력: PBR ≥ 2 는 결정론. 도메인은 키워드 명중 시 결정론, 아니면 LLM 정성 판단.
    const pbrOk = pbr === undefined ? undefined : pbr >= 2;
    let tech: CriterionResult;
    if (pbrOk === false) tech = R('기술력', false, `PBR ${fmt(pbr, '')} < 2 (기술 프리미엄 미인정)`);
    else if (pbrOk === undefined) tech = R('기술력', undefined, 'PBR 데이터 없음');
    else if (!biz) tech = R('기술력', false, `PBR ${fmt(pbr, '')} ≥2 이나 '최대 먹거리' 미입력`);
    else if (TECH_KEYWORDS.test(biz)) tech = R('기술력', true, `PBR ${fmt(pbr, '')} ≥2 + 최대먹거리 '${biz}' 기술영역`);
    else tech = { keyword: '기술력', state: 'llm', detail: `PBR ${fmt(pbr, '')} ≥2, 최대먹거리 '${biz}' — 기술영역 여부 정성 판단 필요` };
    results.push(tech);

    if (market === 'US') {
        // 미국: 부채비율(D/E) ≤100% AND 시가총액 ≥ $2B (대형·우량 안전판). 유보율 미사용.
        const usCapB = capNative === undefined ? undefined : capNative / 1e9;
        const usStable = debt === undefined || usCapB === undefined ? undefined : (debt <= 100 && usCapB >= 2);
        results.push(R('안정성', usStable,
            `부채비율(D/E) ${fmt(debt)} ≤100% AND 시총 ${usCapB === undefined ? '-' : '$' + usCapB.toFixed(1) + 'B'} ≥$2B`));
    } else {
        // 한국 — 유보율·시총에 더해 부채비율(있으면) ≤100% 동반 요구. 유보율은 자본금
        // 크기에 왜곡되는 지표라 단독으론 약함 — 부채 가드가 실질 안전판.
        const stabilityBase = ret === undefined || cap === undefined ? undefined : (ret >= 3000 && cap >= 5000);
        const stabilityDebtOk = debt === undefined ? true : debt <= 100;
        results.push(R('안정성',
            stabilityBase === undefined ? undefined : (stabilityBase && stabilityDebtOk),
            `유보율 ${fmt(ret)} ≥3,000% AND 시총 ${cap === undefined ? '-' : cap.toLocaleString() + '억'} ≥5,000억`
            + (debt !== undefined ? ` AND 부채비율 ${fmt(debt)} ≤100%` : '')));
    }

    results.push(R('PBR', pbr === undefined ? undefined : pbr <= 1.5,
        `PBR ${fmt(pbr, '')} (기준 ≤1.5)`));

    // PER — 가격 매력(이익수익률). 데이터가 이미 있는데 안 쓰던 지표.
    // 2026 시장 평균 PER ~20배 환경에서 ≤12 는 뚜렷한 저평가 신호.
    results.push(R('PER', per === undefined ? undefined : per <= 12,
        `PER ${fmt(per, '배')} (기준 ≤12배)`));

    const capText = market === 'US'
        ? (capNative === undefined ? '-' : `$${(capNative / 1e9).toFixed(1)}B`)
        : (cap === undefined ? '-' : `${cap.toLocaleString()}억`);
    return {
        results,
        dataSource: fresh
            ? `${fresh.source ?? (market === 'US' ? 'Yahoo' : 'Naver')} 실시간 ${now.toISOString().slice(0, 10)}`
            : 'stocks.json 저장값',
        numbers: {
            ROE: fmt(roe), 영업이익률: fmt(opm), 유보율: market === 'US' ? 'N/A(미보고)' : fmt(ret),
            PBR: fmt(pbr, ''), PER: fmt(per, '배'),
            부채비율: fmt(debt), 시가총액: capText,
            '매출 YoY': fmt(revYoY), '영업이익 YoY': fmt(opYoY),
        },
    };
}

// ── 판정 + 대표 키워드 선택 (사용자 명시 규칙의 코드화) ─────────────────────
const PRIORITY: Record<string, string[]> = {
    '스윙/중기': ['ROE', '성장성', '유동성', '수익성'],
    '장기투자': ['성장성', '유동성', '기술력', '영업효율'],
    '저평가우량주': ['PBR', 'PER', 'ROE', '성장성', '수익성', '안정성'],
};

export interface Verdict {
    /** "충족 (ROE, 성장성, 유동성)" / "미충족 (사유: …)" — signalClassifier 계약 유지. */
    text: string;
    passed: string[];
    failed: string[];
}

/** 기술력의 LLM 정성 판단 결과(techPass)를 반영해 최종 판정·대표 3개를 결정. */
export function buildVerdict(ev: CriteriaEvaluation, style: Stock['투자성향'], techPass?: boolean): Verdict {
    const state = (r: CriterionResult): CriterionState =>
        r.keyword === '기술력' && r.state === 'llm' ? (techPass === true ? 'pass' : 'fail') : r.state;
    const passed = ev.results.filter(r => state(r) === 'pass');
    const failed = ev.results.filter(r => state(r) === 'fail' || state(r) === 'unknown');
    const passedNames = passed.map(r => r.keyword);

    if (passed.length < 3) {
        const weak = failed.slice(0, 2).map(r => r.detail).join(' / ') || '데이터 부족';
        return { text: `미충족 (사유: ${weak})`, passed: passedNames, failed: failed.map(r => r.keyword) };
    }

    // 대표 3개: 투자성향 우선 키워드 → 나머지 통과 키워드 순.
    const prio = PRIORITY[style || '스윙/중기'] || PRIORITY['스윙/중기'];
    const ordered = [
        ...prio.filter(k => passedNames.includes(k)),
        ...passedNames.filter(k => !prio.includes(k)),
    ];
    const top3 = ordered.slice(0, 3)
        .map(k => passed.find(r => r.keyword === k)!)
        .map(r => r.label || r.keyword);
    return { text: `충족 (${top3.join(', ')})`, passed: passedNames, failed: failed.map(r => r.keyword) };
}
