#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
webcam_capture_to_excel.py (최종판: 요구사항 반영)
- 바코드 없음
- SPACE: 캡처(미리보기에는 안내 문구, 저장은 원본)
- ENTER: 상품명(C) → 판매가(E) → 재고(G) 입력 순서
- 파일명/대표이미지URL/상세설명URL: 공백을 '_'로 치환하여 저장 (상품명은 공백 유지)
- 대표이미지(R)=URL, 상세설명(T)=<img>, 강제열값(B,D,F,Y,AD,AE,AF,AG,AH,AI,AP,AQ,BU,BW,AY)
"""

import os, cv2, re, sys, argparse, datetime
from pathlib import Path
from typing import Dict, List, Optional
import numpy as np
from openpyxl import load_workbook

# ===== 기본 경로 =====
DEFAULT_BASE_DIR = Path(r"Y:\AUTO EXCEL")
DEFAULT_EXCEL_NAME = "ExcelSaveTemplate_250311.xlsx"
DEFAULT_IMAGE_DIR_NAME = "CapturedImages"
DEFAULT_SHEET_NAME = "일괄등록"
IMAGE_URL_PREFIX = "https://realcom.kr/captured/"

# ===== 강제 입력될 열 =====
FORCE_BY_COL_LETTER: Dict[str, str] = {
    "B":  "50002922",            # 카테고리코드
    "D":  "신상품",               # 상품상태
    "F":  "과세상품",             # 부가세
    "Y":  "0200037",             # 원산지코드
    "AD": "3257219",             # 배송비 템플릿 코드
    "AE": "택배, 소포, 등기",     # 배송방법
    "AF": "LOGEN",               # 택배사코드
    "AG": "유료",                # 배송비유형
    "AH": "4000",                # 기본배송비
    "AI": "선결제",               # 배송비 결제방식
    "AP": "4000",                # 반품배송비
    "AQ": "8000",                # 교환배송비
    "BU": "Y",                   # 구매평 노출여부
    "BW": "N",                   # 알림받기 동의 고객전용 여부
    "AY": "3260118",             # A/S 템플릿코드 (요청사항)
}

REP_HEADER_ALIASES: List[str] = [
    "대표이미지", "대표이미지파일명", "대표이미지 파일명", "대표사진", "대표", "mainimage", "thumb"
]

# ===== 유틸 =====
def ensure_dir(p: Path): p.mkdir(parents=True, exist_ok=True)

def sanitize_filename_no_spaces(name: str) -> str:
    """
    파일/URL용: 모든 공백(연속 포함)을 '_'로 치환하고,
    허용하지 않는 문자는 '_'로 대체.
    """
    s = re.sub(r"\s+", "_", str(name).strip())
    s = re.sub(r"[^0-9A-Za-z가-힣_\-\.]+", "_", s)
    # 앞뒤의 점/언더바 정리
    s = s.strip("._")
    return s or "image"

def crop_center_square(img: np.ndarray, size: int = 1000) -> np.ndarray:
    h, w = img.shape[:2]
    s = min(h, w)
    y1 = (h - s) // 2
    x1 = (w - s) // 2
    return cv2.resize(img[y1:y1+s, x1:x1+s], (size, size), interpolation=cv2.INTER_AREA)

def save_square1000(img: np.ndarray, path: str):
    p = Path(path)
    try:
        p.parent.mkdir(parents=True, exist_ok=True)
    except Exception as e:
        print(f"[ERR] 디렉토리 생성 실패: {p.parent}  -> {e}")
        raise
    sq = crop_center_square(img, 1000)
    ok = cv2.imwrite(str(p), sq)
    if not ok:
        print(f"[ERR] OpenCV 저장 실패: {p}")
        try:
            from PIL import Image
            Image.fromarray(cv2.cvtColor(sq, cv2.COLOR_BGR2RGB)).save(str(p), format="JPEG")
            print(f"[OK] Pillow로 재시도 저장 성공: {p}")
        except Exception as e:
            print(f"[ERR] Pillow 저장도 실패: {e}")
            raise

def col_letter_to_index(s: str) -> Optional[int]:
    if not s: return None
    s = s.strip().upper()
    if not re.fullmatch(r"[A-Z]+", s): return None
    n = 0
    for ch in s: n = n*26 + (ord(ch)-64)
    return n

def detect_header_row(ws, upto: int = 30) -> int:
    best_r, best = 1, -1
    keys = ["대표", "이미지", "상품명", "판매가", "원산지", "배송", "상태", "부가세", "상세설명", "카테고리", "재고"]
    for r in range(1, min(ws.max_row, upto)+1):
        vals = [c.value for c in ws[r] if c.value]
        if not vals: continue
        nvals = [re.sub(r"[\s\-\_/\,\.\(\)\[\]{}]+", "", str(v)).lower() for v in vals]
        score = sum(1 for k in keys for nv in nvals if k in nv) + len(nvals)*0.05
        if score > best: best, best_r = score, r
    return best_r if best_r > 0 else 1

def find_header_col_by_name(ws, header_row: int, aliases: List[str]) -> Optional[int]:
    headers = [c.value if c.value is not None else "" for c in ws[header_row]]
    norm = lambda s: re.sub(r"[\s\-\_/\,\.\(\)\[\]{}]+", "", str(s)).lower()
    aset = [norm(a) for a in aliases]
    for i, h in enumerate(headers, start=1):
        nh = norm(h)
        if any(a == nh or a in nh for a in aset):
            return i
    return None

def robust_save(wb, excel_path: Path):
    try:
        wb.save(excel_path)
        print(f"[SAVED] {excel_path}")
    except PermissionError:
        tmp = excel_path.with_suffix(".tmp.xlsx")
        wb.save(tmp)
        print(f"[TMP SAVED] {tmp} (엑셀 닫고 원본 교체 필요)")

def parse_int_like(s: str) -> Optional[int]:
    if s is None: return None
    s = s.strip()
    if not s: return None
    s = re.sub(r"[^\d\-]", "", s)
    if not s: return None
    try: return int(s)
    except: return None

def next_available_name(base: Path) -> Path:
    """이미 존재하면 -1, -2 … 붙여서 충돌 회피"""
    if not base.exists():
        return base
    stem, suf = base.stem, base.suffix
    i = 1
    while True:
        cand = base.with_name(f"{stem}-{i}{suf}")
        if not cand.exists():
            return cand
        i += 1

# ===== 메인 =====
def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--base-dir", default=str(DEFAULT_BASE_DIR))
    ap.add_argument("--excel", default=None)
    ap.add_argument("--sheet", default=None)
    ap.add_argument("--header-row", type=int, default=0)
    ap.add_argument("--rep-col-letter", default="R")
    ap.add_argument("--name-col-letter", default="C")
    ap.add_argument("--price-col-letter", default="E")
    ap.add_argument("--stock-col-letter", default="G")
    ap.add_argument("--image-dir", default=None)
    ap.add_argument("--image-url-prefix", default=IMAGE_URL_PREFIX)
    ap.add_argument("--width", type=int, default=1920)
    ap.add_argument("--height", type=int, default=1080)
    args = ap.parse_args()

    base_dir = Path(args.base_dir).expanduser().resolve()
    excel_path = Path(args.excel).expanduser() if args.excel else (base_dir / DEFAULT_EXCEL_NAME)
    image_dir = Path(args.image_dir).expanduser() if args.image_dir else (base_dir / DEFAULT_IMAGE_DIR_NAME)
    if not image_dir.is_absolute(): image_dir = base_dir / image_dir
    ensure_dir(image_dir)

    if not excel_path.exists():
        print(f"[ERR] Excel not found: {excel_path}"); sys.exit(1)

    wb = load_workbook(excel_path, data_only=False, read_only=False)
    if args.sheet and args.sheet in wb.sheetnames:
        ws = wb[args.sheet]
    elif DEFAULT_SHEET_NAME in wb.sheetnames:
        ws = wb[DEFAULT_SHEET_NAME]
    else:
        ws = wb[wb.sheetnames[0]]

    header_row = args.header_row if args.header_row > 0 else detect_header_row(ws)
    data_start_row = header_row + 1
    print(f"[i] Sheet={ws.title}  HeaderRow={header_row}  DataStart={data_start_row}")

    col_rep   = col_letter_to_index(args.rep_col_letter) or find_header_col_by_name(ws, header_row, REP_HEADER_ALIASES)
    if not col_rep:
        last_col = ws.max_column
        col_rep = last_col + 1
        ws.cell(row=header_row, column=col_rep, value="대표이미지")
        print(f"[i] '대표이미지' 열 생성 → col {col_rep}")

    col_name  = col_letter_to_index(args.name_col_letter)
    col_price = col_letter_to_index(args.price_col_letter)
    col_stock = col_letter_to_index(args.stock_col_letter)
    col_T     = col_letter_to_index("T")

    # 강제값 맵
    force_idx_map = {col_letter_to_index(k): v for k, v in FORCE_BY_COL_LETTER.items()}
    force_idx_map = {k: v for k, v in force_idx_map.items() if k is not None}

    # 카메라
    cap = cv2.VideoCapture(0)
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, args.width)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, args.height)
    print("SPACE=capture  ENTER=save  R=recapture  Q=quit")

    captured_raw = None  # 저장용 원본
    while True:
        ok, frame = cap.read()
        if not ok or frame is None:
            print("[ERR] Camera read failed"); break

        # 라이브 미리보기(상단 안내)
        disp = frame.copy()
        cv2.putText(disp, "SPACE=capture ENTER=save R=recapture Q=quit",
                    (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
        cv2.imshow("Webcam", disp)
        key = cv2.waitKey(1) & 0xff

        if key == ord('q'):
            break

        if key == 32:  # SPACE → 캡처
            captured_raw = frame.copy()   # 저장용 원본
            preview = captured_raw.copy() # 미리보기만 오버레이
            cv2.putText(preview, "Captured. Press ENTER to save, R to recapture.",
                        (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
            cv2.imshow("Captured", preview)

        if key == ord('r'):  # 다시 찍기
            try: cv2.destroyWindow("Captured")
            except: pass
            captured_raw = None

        if key == 13 and captured_raw is not None:  # ENTER → 저장/입력
            # 1) 상품명 입력 (공백 허용, 엑셀에는 그대로)
            product_name = input("상품명 입력(C열): ").strip()
            # 파일/URL용 안전 이름(공백→'_')
            base_stub = sanitize_filename_no_spaces(product_name) if product_name else datetime.datetime.now().strftime("%Y%m%d_%H%M%S")

            # 2) 판매가, 재고
            raw_price = input("판매가 입력(E열, 예: 12340 / 엔터=건너뛰기): ").strip()
            raw_stock = input("재고수량 입력(G열, 예: 5 / 엔터=건너뛰기): ").strip()
            price_val = parse_int_like(raw_price)
            stock_val = parse_int_like(raw_stock)

            # 3) 이미지 저장 (파일명 공백없음)
            fname = base_stub + ".jpg"
            save_path = next_available_name(image_dir / fname)
            print(f"[SAVE] 이미지 저장 시도: {save_path}")
            save_square1000(captured_raw, str(save_path))
            print(f"[OK] 이미지 저장 완료: {save_path}")

            # 4) URL (공백없음)
            image_url = args.image_url_prefix.rstrip('/') + "/" + save_path.name

            # 5) 행 선택
            row_idx = max(ws.max_row + 1, data_start_row)

            # 6) 엑셀 기록
            # 대표이미지(URL)
            ws.cell(row=row_idx, column=col_rep, value=image_url)
            # 상품명(C) = 사용자 입력 그대로(공백 유지)
            if col_name:  ws.cell(row=row_idx, column=col_name,  value=product_name if product_name else base_stub)
            # 상세설명(T) = <img src="..."> (URL 공백없음)
            if col_T:     ws.cell(row=row_idx, column=col_T,     value=f'<img src="{image_url}">')
            # 판매가(E)
            if col_price and price_val is not None: ws.cell(row=row_idx, column=col_price, value=price_val)
            # 재고수량(G)
            if col_stock and stock_val is not None: ws.cell(row=row_idx, column=col_stock, value=stock_val)

            # 강제값 채우기 (B,D,F,Y,AD,AE,AF,AG,AH,AI,AP,AQ,BU,BW,AY)
            for idx, val in force_idx_map.items():
                ws.cell(row=row_idx, column=idx, value=val)

            print(f"[OK] Row {row_idx} 저장 완료")
            robust_save(wb, excel_path)

            try: cv2.destroyWindow("Captured")
            except: pass
            captured_raw = None  # 한 건 처리 후 초기화

    cap.release()
    try: cv2.destroyAllWindows()
    except: pass

if __name__ == "__main__":
    main()
