#!/usr/bin/env python3
"""
Meta Marketing API - 리쿠르팅 광고 캠페인 셋업 스크립트

담당: 엔키 (개발5팀)
생성일: 2026-04-06

실행 예시:
    python meta_campaign_setup.py
    python meta_campaign_setup.py --page-id 1234567890
    python meta_campaign_setup.py --dry-run
    python meta_campaign_setup.py --page-id 1234567890 --dry-run
"""

import argparse
import json
import os
import sys
from datetime import datetime
from pathlib import Path

# 프로젝트 루트를 sys.path에 추가
_WORKSPACE = Path("/home/jay/workspace")
if str(_WORKSPACE) not in sys.path:
    sys.path.insert(0, str(_WORKSPACE))

from utils.meta_ads_client import MetaAdsClient  # type: ignore[import-not-found]

# facebook_business SDK (Ad 직접 생성에 필요)
try:
    from facebook_business.adobjects.ad import Ad
    from facebook_business.adobjects.adaccount import AdAccount
except ImportError as e:
    print(f"[ERROR] facebook_business SDK를 찾을 수 없습니다: {e}")
    sys.exit(1)

# ---------------------------------------------------------------------------
# 상수 정의
# ---------------------------------------------------------------------------

OUTPUT_DIR = Path("/home/jay/workspace/teams/dev5-team/output")
OUTPUT_FILE = OUTPUT_DIR / "meta_campaign_result.json"

LANDING_URL = "https://incar-top1.tistory.com"

BANNER_BASE = Path("/home/jay/workspace/output/banners/versions")

# 셀 → (버전, 디렉토리명, 브랜드)
CELL_MAP = [
    ("v-round2", "cell-1-incar-fair", "인카금융서비스"),
    ("v-round2", "cell-2-incar-leader", "인카금융서비스"),
    ("v-round2", "cell-3-incar-support", "인카금융서비스"),
    ("v1460", "cell-4-ga-fair", "GA"),
    ("v1460", "cell-5-ga-leader", "GA"),
    ("v1460", "cell-6-ga-support", "GA"),
    ("v-round2", "cell-7-snu-fair", "서울대보험쌤"),
    ("v-round2", "cell-8-snu-leader", "서울대보험쌤"),
    ("v-round2", "cell-9-snu-support", "서울대보험쌤"),
]

# 광고세트 브랜드 약칭 (캠페인 약칭 뒤에 붙음)
ADSET_BRANDS = ["인카금융서비스", "GA", "서울대보험쌤"]

# 공통 타겟팅
COMMON_TARGETING = {
    "geo_locations": {
        "regions": [
            {"key": "2004"},  # 서울
            {"key": "2006"},  # 경기도
            {"key": "2001"},  # 충청북도
            {"key": "2010"},  # 충청남도 (대전/세종 포함)
        ],
    },
    "age_min": 30,
    "age_max": 55,
    "flexible_spec": [
        {
            "interests": [
                {"id": "6003217093576", "name": "보험"},
                {"id": "6004037215009", "name": "구직"},
                {"id": "6003074954515", "name": "영업"},
                {"id": "6003115782742", "name": "보험계약"},
            ]
        }
    ],
    "targeting_automation": {
        "advantage_audience": 0,
    },
}

# 브랜드별 광고 메시지
BRAND_MESSAGES = {
    "인카금융서비스": "인카금융서비스와 함께 성공적인 보험 커리어를 시작하세요! 경력직/신입 모두 환영합니다.",
    "GA": "GA(General Agency)에서 당신의 보험 전문가 역량을 키워보세요. 지금 바로 지원하세요!",
    "서울대보험쌤": "서울대 출신 보험 전문가들과 함께하는 성장의 기회! 서울대보험쌤 팀에 합류하세요.",
}


# ---------------------------------------------------------------------------
# 유틸리티
# ---------------------------------------------------------------------------


def log(msg: str) -> None:
    """타임스탬프 포함 콘솔 로그."""
    ts = datetime.now().strftime("%H:%M:%S")
    print(f"[{ts}] {msg}")


def image_path(version: str, cell_dir: str) -> str:
    """배너 이미지 절대 경로를 반환한다."""
    return str(BANNER_BASE / version / cell_dir / "meta-feed-1080x1080.png")


# ---------------------------------------------------------------------------
# 드라이런 모드
# ---------------------------------------------------------------------------


def dry_run_report(page_id: str | None) -> None:
    """실제 API 호출 없이 실행 구조를 출력한다."""
    pixel_id = os.environ.get("META_PIXEL_ID", "1461062562329883")

    print("\n" + "=" * 60)
    print("  DRY-RUN 모드 — 실제 API 호출 없음")
    print("=" * 60)

    print("\n[1] MetaAdsClient 초기화 + 토큰/계정 유효성 확인")

    print("\n[2] 배너 이미지 9장 업로드")
    for version, cell_dir, brand in CELL_MAP:
        p = image_path(version, cell_dir)
        exists = "✓" if Path(p).exists() else "✗ 파일없음"
        print(f"    {exists}  {cell_dir}  →  {p}")

    print("\n[3] 캠페인 2개 생성 (PAUSED)")
    campaigns_info = [
        {
            "name": "리쿠르팅_리드_2026Q2",
            "objective": "OUTCOME_LEADS",
            "alias": "리드",
            "daily_budget": 1050,
            "opt_goal": "LEAD_GENERATION",
            "billing": "IMPRESSIONS",
            "promoted_object": {"pixel_id": pixel_id, "custom_event_type": "LEAD"},
        },
        {
            "name": "리쿠르팅_잠재고객_2026Q2",
            "objective": "OUTCOME_TRAFFIC",
            "alias": "잠재고객",
            "daily_budget": 1050,
            "opt_goal": "LINK_CLICKS",
            "billing": "IMPRESSIONS",
            "promoted_object": None,
        },
    ]
    for c in campaigns_info:
        print(f"    - {c['name']}  objective={c['objective']}  daily_budget={c['daily_budget']}¢")

    print("\n[4] 광고세트 6개 생성 (PAUSED, 각 350¢)")
    for c in campaigns_info:
        for brand in ADSET_BRANDS:
            name = f"{c['alias']}_{brand}"
            print(f"    - {name}  opt={c['opt_goal']}  billing={c['billing']}")

    if page_id:
        print(f"\n[5] 크리에이티브 9개 + 광고 18개 생성 (page_id={page_id})")
        for brand in ADSET_BRANDS:
            cells = [cell_dir for _, cell_dir, b in CELL_MAP if b == brand]
            for cell_dir in cells:
                print(f"    크리에이티브: {brand}_{cell_dir}")
        print("    → 각 광고세트(6개) × 3개 크리에이티브 = 18개 광고 (PAUSED)")
    else:
        print("\n[5] 크리에이티브/광고 생성 스킵 (page_id 없음)")

    print("\n[6] 결과 JSON 저장")
    print(f"    → {OUTPUT_FILE}")
    print("\n" + "=" * 60 + "\n")


# ---------------------------------------------------------------------------
# 메인 로직
# ---------------------------------------------------------------------------


def run(page_id: str | None) -> None:
    """실제 캠페인 세팅을 실행한다."""
    result: dict = {
        "timestamp": datetime.now().isoformat(),
        "account_id": "",
        "currency": "USD",
        "images": [],
        "campaigns": [],
        "adsets": [],
        "creatives": [],
        "ads": [],
        "errors": [],
        "blocked": None,
    }

    # ------------------------------------------------------------------
    # 1. MetaAdsClient 초기화 + 유효성 확인
    # ------------------------------------------------------------------
    log("[1] MetaAdsClient 초기화 중...")
    try:
        client = MetaAdsClient()
    except Exception as e:
        log(f"[ERROR] 클라이언트 초기화 실패: {e}")
        result["errors"].append({"step": "init", "error": str(e)})
        _save(result)
        sys.exit(1)

    # 토큰 유효성 확인
    try:
        token_info = client.check_token()
        if not token_info.get("is_valid", False):
            log("[ERROR] 액세스 토큰이 유효하지 않습니다.")
            result["errors"].append({"step": "check_token", "error": "is_valid=False"})
            _save(result)
            sys.exit(1)
        log(f"  토큰 유효 확인 (app_id={token_info.get('app_id')})")
    except Exception as e:
        log(f"[WARN] 토큰 유효성 확인 실패 (계속 진행): {e}")
        result["errors"].append({"step": "check_token", "error": str(e)})

    # 계정 정보 확인
    try:
        account_info = client.get_account_info()
        account_id = account_info.get("id", os.environ.get("META_AD_ACCOUNT_ID", ""))
        if not account_id.startswith("act_"):
            account_id = f"act_{account_id}"
        result["account_id"] = account_id
        result["currency"] = account_info.get("currency", "USD")
        log(f"  계정 확인: {account_id}  통화={result['currency']}  상태={account_info.get('account_status')}")
    except Exception as e:
        log(f"[ERROR] 계정 정보 조회 실패: {e}")
        result["errors"].append({"step": "get_account_info", "error": str(e)})
        _save(result)
        sys.exit(1)

    # SDK AdAccount 인스턴스 (Ad 직접 생성용)
    ad_account = AdAccount(result["account_id"])

    pixel_id = os.environ.get("META_PIXEL_ID", "1461062562329883")

    # ------------------------------------------------------------------
    # 2. 배너 이미지 9장 업로드
    # ------------------------------------------------------------------
    log("[2] 배너 이미지 업로드 (9장)...")

    # image_hash_map: cell_dir → image_hash
    image_hash_map: dict[str, str] = {}

    for version, cell_dir, brand in CELL_MAP:
        path = image_path(version, cell_dir)
        try:
            h = client.upload_image(path)
            image_hash_map[cell_dir] = h
            result["images"].append({"cell": cell_dir, "brand": brand, "image_hash": h})
            log(f"  업로드 완료: {cell_dir}  hash={h}")
        except Exception as e:
            log(f"  [ERROR] 이미지 업로드 실패 ({cell_dir}): {e}")
            result["errors"].append({"step": "upload_image", "cell": cell_dir, "error": str(e)})

    # ------------------------------------------------------------------
    # 3. 캠페인 2개 생성
    # ------------------------------------------------------------------
    log("[3] 캠페인 생성 (2개, PAUSED)...")

    campaigns_meta = [
        {
            "name": "리쿠르팅_리드_2026Q2",
            "objective": "OUTCOME_LEADS",
            "alias": "리드",
            "daily_budget": None,  # adset-level budget 사용
            "opt_goal": "OFFSITE_CONVERSIONS",  # LEAD_GENERATION은 OUTCOME_LEADS와 호환 안됨
            "billing": "IMPRESSIONS",
            "promoted_object": {"pixel_id": pixel_id, "custom_event_type": "LEAD"},
        },
        {
            "name": "리쿠르팅_잠재고객_2026Q2",
            "objective": "OUTCOME_TRAFFIC",
            "alias": "잠재고객",
            "daily_budget": None,  # adset-level budget 사용
            "opt_goal": "LINK_CLICKS",
            "billing": "IMPRESSIONS",
            "promoted_object": None,
        },
    ]

    created_campaigns: list[dict] = []

    for cm in campaigns_meta:
        try:
            camp = client.create_campaign(
                name=cm["name"],
                objective=cm["objective"],
                status="PAUSED",
                daily_budget=cm["daily_budget"],
                special_ad_categories=[],
            )
            camp_id = camp.get("id", "")
            record = {
                "name": cm["name"],
                "id": camp_id,
                "objective": cm["objective"],
                "daily_budget": cm["daily_budget"],
                "alias": cm["alias"],
                "opt_goal": cm["opt_goal"],
                "billing": cm["billing"],
                "promoted_object": cm["promoted_object"],
            }
            created_campaigns.append(record)
            result["campaigns"].append(
                {
                    "name": record["name"],
                    "id": record["id"],
                    "objective": record["objective"],
                    "daily_budget": record["daily_budget"],
                }
            )
            log(f"  캠페인 생성 완료: {cm['name']}  id={camp_id}")
        except Exception as e:
            log(f"  [ERROR] 캠페인 생성 실패 ({cm['name']}): {e}")
            result["errors"].append({"step": "create_campaign", "name": cm["name"], "error": str(e)})

    # ------------------------------------------------------------------
    # 4. 광고세트 6개 생성 (각 캠페인당 3개)
    # ------------------------------------------------------------------
    log("[4] 광고세트 생성 (6개, PAUSED)...")

    # adset_map: (campaign_alias, brand) → adset_id
    adset_map: dict[tuple[str, str], str] = {}

    for camp_record in created_campaigns:
        camp_id = camp_record["id"]
        alias = camp_record["alias"]
        opt_goal = camp_record["opt_goal"]
        billing = camp_record["billing"]
        promoted_object = camp_record["promoted_object"]

        for brand in ADSET_BRANDS:
            adset_name = f"{alias}_{brand}"

            # 타겟팅: 공통 타겟팅 복사 후 promoted_object 추가
            targeting = dict(COMMON_TARGETING)

            # promoted_object는 타겟팅이 아니라 광고세트 params에 별도로 전달해야 함
            # MetaAdsClient.create_adset은 promoted_object를 직접 지원하지 않으므로
            # 리드 캠페인의 경우 SDK를 직접 사용
            try:
                if promoted_object:
                    adset = _create_adset_with_promoted_object(
                        ad_account=ad_account,
                        campaign_id=camp_id,
                        name=adset_name,
                        daily_budget=350,
                        targeting=targeting,
                        optimization_goal=opt_goal,
                        billing_event=billing,
                        promoted_object=promoted_object,
                    )
                    adset_id = adset.get("id", "")
                else:
                    adset = client.create_adset(
                        campaign_id=camp_id,
                        name=adset_name,
                        daily_budget=350,
                        targeting=targeting,
                        optimization_goal=opt_goal,
                        billing_event=billing,
                        status="PAUSED",
                    )
                    adset_id = adset.get("id", "")

                adset_map[(alias, brand)] = adset_id
                result["adsets"].append(
                    {
                        "name": adset_name,
                        "id": adset_id,
                        "campaign_id": camp_id,
                        "daily_budget": 350,
                    }
                )
                log(f"  광고세트 생성 완료: {adset_name}  id={adset_id}")

            except Exception as e:
                log(f"  [ERROR] 광고세트 생성 실패 ({adset_name}): {e}")
                result["errors"].append({"step": "create_adset", "name": adset_name, "error": str(e)})

    # ------------------------------------------------------------------
    # 5. 크리에이티브 + 광고 생성 (page_id 필요)
    # ------------------------------------------------------------------
    if not page_id:
        log("[5] 크리에이티브/광고 생성 스킵 (--page-id 미지정)")
        result["blocked"] = "Facebook Page 미연결 - 크리에이티브/광고 생성 불가"
    else:
        log(f"[5] 크리에이티브 + 광고 생성 (page_id={page_id})...")

        # 크리에이티브 생성: 9개 (브랜드별 3개 셀)
        # creative_map: cell_dir → creative_id
        creative_map: dict[str, str] = {}

        for version, cell_dir, brand in CELL_MAP:
            img_hash = image_hash_map.get(cell_dir)
            if not img_hash:
                log(f"  [SKIP] 크리에이티브 스킵 ({cell_dir}): image_hash 없음")
                continue

            creative_name = f"creative_{cell_dir}"
            message = BRAND_MESSAGES.get(brand, "인카금융서비스 리쿠르팅 — 지금 지원하세요!")
            try:
                creative = client.create_creative(
                    name=creative_name,
                    image_hash=img_hash,
                    page_id=page_id,
                    message=message,
                    link=LANDING_URL,
                )
                creative_id = creative.get("id", "")
                creative_map[cell_dir] = creative_id
                result["creatives"].append(
                    {
                        "name": creative_name,
                        "id": creative_id,
                        "cell": cell_dir,
                        "brand": brand,
                        "image_hash": img_hash,
                    }
                )
                log(f"  크리에이티브 생성 완료: {creative_name}  id={creative_id}")
            except Exception as e:
                log(f"  [ERROR] 크리에이티브 생성 실패 ({creative_name}): {e}")
                result["errors"].append({"step": "create_creative", "cell": cell_dir, "error": str(e)})

        # 광고 생성: 각 광고세트 × 브랜드별 셀 3개 = 총 18개
        ad_count = 0
        for camp_record in created_campaigns:
            alias = camp_record["alias"]
            for brand in ADSET_BRANDS:
                adset_id = adset_map.get((alias, brand))
                if not adset_id:
                    log(f"  [SKIP] 광고 스킵: 광고세트 없음 ({alias}_{brand})")
                    continue

                # 해당 브랜드의 셀 목록
                brand_cells = [(cell_dir, version) for version, cell_dir, b in CELL_MAP if b == brand]

                for cell_dir, _ in brand_cells:
                    creative_id = creative_map.get(cell_dir)
                    if not creative_id:
                        log(f"  [SKIP] 광고 스킵: 크리에이티브 없음 ({cell_dir})")
                        continue

                    ad_name = f"ad_{alias}_{brand}_{cell_dir}"
                    try:
                        ad_result = _create_ad(
                            ad_account=ad_account,
                            adset_id=adset_id,
                            creative_id=creative_id,
                            name=ad_name,
                            pixel_id=pixel_id,
                        )
                        ad_id = ad_result.get("id", "")
                        result["ads"].append(
                            {
                                "name": ad_name,
                                "id": ad_id,
                                "adset_id": adset_id,
                                "creative_id": creative_id,
                            }
                        )
                        ad_count += 1
                        log(f"  광고 생성 완료: {ad_name}  id={ad_id}")
                    except Exception as e:
                        log(f"  [ERROR] 광고 생성 실패 ({ad_name}): {e}")
                        result["errors"].append({"step": "create_ad", "name": ad_name, "error": str(e)})

        log(f"  총 {ad_count}개 광고 생성 완료")

    # ------------------------------------------------------------------
    # 6. 결과 JSON 저장
    # ------------------------------------------------------------------
    log("[6] 결과 저장 중...")
    _save(result)

    # 요약 출력
    log("=" * 50)
    log("실행 완료 요약")
    log(f"  계정: {result['account_id']}  통화: {result['currency']}")
    log(f"  이미지: {len(result['images'])}개")
    log(f"  캠페인: {len(result['campaigns'])}개")
    log(f"  광고세트: {len(result['adsets'])}개")
    log(f"  크리에이티브: {len(result['creatives'])}개")
    log(f"  광고: {len(result['ads'])}개")
    log(f"  오류: {len(result['errors'])}건")
    if result.get("blocked"):
        log(f"  주의: {result['blocked']}")
    log(f"  결과 파일: {OUTPUT_FILE}")
    log("=" * 50)


# ---------------------------------------------------------------------------
# 헬퍼: promoted_object를 포함한 광고세트 직접 생성
# ---------------------------------------------------------------------------


def _create_adset_with_promoted_object(
    ad_account: AdAccount,
    campaign_id: str,
    name: str,
    daily_budget: int,
    targeting: dict,
    optimization_goal: str,
    billing_event: str,
    promoted_object: dict,
) -> dict:
    """
    promoted_object가 필요한 광고세트를 facebook_business SDK로 직접 생성한다.
    MetaAdsClient.create_adset은 promoted_object 파라미터를 지원하지 않으므로
    AdAccount.create_ad_set을 직접 호출한다.
    """
    from facebook_business.adobjects.adset import AdSet

    params = {
        AdSet.Field.name: name,
        AdSet.Field.campaign_id: campaign_id,
        AdSet.Field.daily_budget: daily_budget,
        AdSet.Field.targeting: targeting,
        AdSet.Field.optimization_goal: optimization_goal,
        AdSet.Field.billing_event: billing_event,
        AdSet.Field.status: "PAUSED",
        AdSet.Field.promoted_object: promoted_object,
    }
    adset_result = ad_account.create_ad_set(fields=[], params=params)
    data: dict = getattr(adset_result, "export_all_data", lambda: dict(adset_result))()  # type: ignore[arg-type]
    return data


# ---------------------------------------------------------------------------
# 헬퍼: Ad 직접 생성 (AdAccount.create_ad 사용)
# ---------------------------------------------------------------------------


def _create_ad(
    ad_account: AdAccount,
    adset_id: str,
    creative_id: str,
    name: str,
    pixel_id: str,
) -> dict:
    """
    facebook_business SDK의 AdAccount.create_ad를 사용하여 광고를 생성한다.
    MetaAdsClient에 create_ad 메서드가 없으므로 SDK를 직접 사용한다.
    """
    params = {
        Ad.Field.name: name,
        Ad.Field.adset_id: adset_id,
        Ad.Field.creative: {"creative_id": creative_id},
        Ad.Field.status: "PAUSED",
        Ad.Field.tracking_specs: [
            {
                "action.type": ["offsite_conversion"],
                "fb_pixel": [pixel_id],
            }
        ],
    }
    ad_result = ad_account.create_ad(fields=[], params=params)
    data: dict = getattr(ad_result, "export_all_data", lambda: dict(ad_result))()  # type: ignore[arg-type]
    return data


# ---------------------------------------------------------------------------
# 결과 저장
# ---------------------------------------------------------------------------


def _save(result: dict) -> None:
    """결과를 JSON 파일로 저장한다."""
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
    with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
        json.dump(result, f, ensure_ascii=False, indent=2)
    log(f"결과 저장 완료: {OUTPUT_FILE}")


# ---------------------------------------------------------------------------
# 진입점
# ---------------------------------------------------------------------------


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Meta Marketing API 리쿠르팅 캠페인 셋업 스크립트",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=__doc__,
    )
    parser.add_argument(
        "--page-id",
        metavar="PAGE_ID",
        default=None,
        help="Facebook 페이지 ID. 지정 시 크리에이티브/광고 생성 포함. 미지정 시 스킵.",
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        default=False,
        help="실제 API 호출 없이 실행 구조만 출력한다.",
    )
    return parser.parse_args()


if __name__ == "__main__":
    args = parse_args()

    if args.dry_run:
        dry_run_report(page_id=args.page_id)
        sys.exit(0)

    run(page_id=args.page_id)
