"""
update_biz_metadata.py - AWS DataZone에 생성된 Tableau 대시보드 자산의 메타데이터 업데이트

이 스크립트는 다음 기능을 수행합니다:
1. DataZone에서 용어집(glossary) 정보 조회
2. Tableau 데이터베이스에서 대시보드 상세 정보 조회
3. 조회된 정보를 기반으로 DataZone 자산의 메타데이터 업데이트:
   - 디스플레이 이름 및 설명
   - README 양식
   - 비즈니스 메타데이터 양식
   - 관련 용어집 용어 연결

사용법:
    python update_biz_metadata.py --asset-id <asset_id>
    
    필수 인자:
    --asset-id : DataZone 자산 ID
"""

import boto3
import json
import psycopg2
from psycopg2.extras import RealDictCursor
from datetime import datetime
import argparse
import sys

# 설정 상수 정의
AWS_REGION = '{AWS Region 입력}'
DATAZONE_DOMAIN_ID = '{DataZone 도메인 ID 입력}' 
DATAZONE_PROJECT_ID = '{DataZone 프로젝트 ID 입력}'  
DATAZONE_GLOSSARY_NAME = 'bi_asset_glossary'        # 생성한 용어집 이름
DATAZONE_METADATA_FORM_NAME = 'bi_asset_metadata'   # 생성한 메타데이터 양식 이름
DB_SECRET_NAME = ''                                 # (선택사항) Tableau 서버에 연결해서 메타데이터를 로드하기 위한 DB 연결 정보가 저장된 Secret 이름
TABLEAU_SERVER_URL = 'tableau.server.com'           # Tableau 서버 접속을 위한 URL 정보

# AWS 클라이언트 초기화 
datazone_client = boto3.client('datazone', region_name=AWS_REGION)
secrets_client = boto3.client('secretsmanager', region_name=AWS_REGION)

def parse_arguments():
    """
    명령줄 인자를 파싱합니다.
    """
    parser = argparse.ArgumentParser(
        description='AWS DataZone에 Tableau 대시보드 자산의 메타데이터를 업데이트합니다.'
    )

    parser.add_argument(
        '--asset-id',
        type=str,
        help='업데이트할 DataZone 자산 ID (필수)'
    )

    # 인자가 없으면 도움말 출력
    if len(sys.argv) == 1:
        parser.print_help()
        sys.exit(1)

    return parser.parse_args()

def get_db_connection_params(secret_name):
    """
    AWS Secrets Manager에서 데이터베이스 접속 정보를 가져옵니다.
    
    Args:
        secret_name (str): Secrets Manager에 저장된 시크릿 이름
        
    Returns:
        dict: 데이터베이스 접속 파라미터 딕셔너리
    """
    secret = json.loads(secrets_client.get_secret_value(SecretId=secret_name)['SecretString'])

    # 연결 파라미터 구성
    db_params = {
        'host': secret['host'],
        'port': secret['port'],
        'database': secret['dbname'],
        'user': secret['username'],
        'password': secret['password']
    }

    return db_params

def run_query(secret_name, query, params=None):
    """
    AWS Secrets Manager에서 DB 접속 정보를 가져와 쿼리를 실행합니다.
    
    Args:
        secret_name (str): Secrets Manager에 저장된 시크릿 이름
        query (str): 실행할 SQL 쿼리
        params (tuple, optional): 쿼리 파라미터. 기본값은 None.
        
    Returns:
        list: 쿼리 결과를 딕셔너리 리스트 형태로 반환
    """
    # DB 접속 정보 가져오기
    db_params = get_db_connection_params(secret_name)

    # 연결 및 쿼리 실행
    with psycopg2.connect(**db_params) as conn, conn.cursor(cursor_factory=RealDictCursor) as cur:
        cur.execute(query, params)
        return cur.fetchall()

def get_glossary():
    """
    DataZone에서 용어집 용어 목록을 가져옵니다.
    
    Returns:
        dict: 용어집 이름을 키로, 용어 이름-ID 맵핑을 값으로 하는 딕셔너리
    """
    all_items = []
    next_token = None
    search_params = {
        'domainIdentifier': DATAZONE_DOMAIN_ID,
        'searchScope': 'GLOSSARY_TERM'
    }
    
    # 페이지네이션을 처리하며 모든 용어집 용어 가져오기
    while True:
        if next_token:
            search_params['nextToken'] = next_token

        response = datazone_client.search(**search_params)
        all_items.extend(response['items'])

        if 'nextToken' not in response:
            break
        next_token = response['nextToken']

    # glossaryId로 그룹화
    glossary_groups = {}
    for item in all_items:
        glossary_id = item['glossaryTermItem']['glossaryId']
        if glossary_id not in glossary_groups:
            glossary_groups[glossary_id] = {}

        term_name = item['glossaryTermItem']['name']
        term_id = item['glossaryTermItem']['id']
        glossary_groups[glossary_id][term_name] = term_id

    # glossary 이름으로 매핑
    glossary_name_map = {}
    for glossary_id, terms in glossary_groups.items():
        glossary_info = datazone_client.get_glossary(
            domainIdentifier=DATAZONE_DOMAIN_ID,
            identifier=glossary_id
        )
        glossary_name = glossary_info['name']
        glossary_name_map[glossary_name] = terms

    return glossary_name_map

def get_dashboard_info(dashboard_id):
    """
    Tableau 워크북 정보를 데이터베이스에서 조회합니다.
    
    Args:
        dashboard_id (str): 조회할 대시보드(워크북) ID
        
    Returns:
        dict: 대시보드 정보 딕셔너리, 없으면 None
    """
    # 워크북 정보 쿼리
    query = """
    SELECT 
        s.name AS site_name,
        w.id AS id,
        w.name AS name,
        u.friendly_name AS owner,
        w.created_at AS created_date,
        w.updated_at AS last_updated_date,
        w.view_count AS total_views,
        ROUND(w.size / (1024.0 * 1024.0), 2) AS size,
        w.workbook_url AS url,
        w.description AS description
    FROM _workbooks w
    LEFT JOIN sites s ON w.site_id = s.id
    LEFT JOIN _users u ON w.owner_id = u.id and w.site_id = u.site_id 
    WHERE w.id = %s 
    """
    result = run_query(DB_SECRET_NAME, query, (dashboard_id,))
    if result and len(result) > 0:
        return result[0]

    print(f"대시보드 ID {dashboard_id}에 대한 정보를 찾을 수 없습니다.")
    return None

def update_asset_metadata(asset_id, dashboard_info):
    """
    DataZone 자산의 메타데이터를 업데이트합니다.
    
    Args:
        asset_id (str): 업데이트할 DataZone 자산 ID
        dashboard_info (dict): 대시보드 정보 딕셔너리
        
    Returns:
        dict: 자산 리비전 업데이트 응답
    """

    # 용어집 용어 목록 가져오기
    print("용어집 정보 조회 중...")
    glossary_terms = get_glossary()

    # 1. 디스플레이 이름 및 설명 설정
    display_name = f"[{dashboard_info['site_name'].upper()}] {dashboard_info['name']}"
    description = dashboard_info.get('description', '')

    print(f"자산 디스플레이 이름: {display_name}")

    # 2. README 양식 업데이트
    tableau_url = f"https://{TABLEAU_SERVER_URL}/#/site/{dashboard_info['site_name']}/workbooks/{dashboard_info['id']}/views"

    readme_content = f"""## {dashboard_info['name']} ([바로가기]({tableau_url}))

`바로가기`를 클릭하면 해당 Tableau 페이지로 이동합니다.

## 정보
- **설명**: {description}
- **워크북 소유자**: {dashboard_info['owner']}
- **워크북 마지막 업데이트 시간**: {dashboard_info['last_updated_date']}
- **워크북 크기**: {dashboard_info['size']} MB
"""
    
    readme_dict = {"readMe": readme_content}

    readme_form = {
        "content": json.dumps(readme_dict, indent=2, ensure_ascii=False),
        "formName": "AssetCommonDetailsForm"
    }

    # 3. 비즈니스 메타데이터 양식 업데이트
    biz_domain = dashboard_info.get('biz_domain', '')
    tags = dashboard_info.get('tags', None)
    notes = dashboard_info.get('status_notes', None)

    bi_metadata = {
        "bi_type": [],
        "biz_domain": [],
        "tags": tags,
        "notes": notes,
        "owner": dashboard_info['owner'],
        "last_timestamp": datetime.now().isoformat()
    }

    # 용어집에서 해당하는 용어 ID 추가
    if DATAZONE_GLOSSARY_NAME in glossary_terms:
        # Tableau 용어 ID 추가
        if 'tableau' in glossary_terms[DATAZONE_GLOSSARY_NAME]:
            tableau_term_id = glossary_terms[DATAZONE_GLOSSARY_NAME]['tableau']
            bi_metadata["bi_type"].append(tableau_term_id)

        # 부서 용어 ID 추가
        if biz_domain and biz_domain in glossary_terms[DATAZONE_GLOSSARY_NAME]:
            domain_term_id = glossary_terms[DATAZONE_GLOSSARY_NAME][biz_domain]
            bi_metadata["biz_domain"].append(domain_term_id)
    
    # None 값 제거
    bi_metadata = {k: v for k, v in bi_metadata.items() if v}

    metadata_form = {
        "content": json.dumps(bi_metadata, indent=2, ensure_ascii=False),
        "formName": DATAZONE_METADATA_FORM_NAME,
        "typeIdentifier": DATAZONE_METADATA_FORM_NAME
    }

    # 4. 용어집 용어 설정
    glossary_term_ids = []
    if DATAZONE_GLOSSARY_NAME in glossary_terms:
        # Tableau 용어 추가
        if 'tableau' in glossary_terms[DATAZONE_GLOSSARY_NAME]:
            glossary_term_ids.append(glossary_terms[DATAZONE_GLOSSARY_NAME]['tableau'])

        # 부서 용어 추가
        if biz_domain and biz_domain in glossary_terms[DATAZONE_GLOSSARY_NAME]:
            glossary_term_ids.append(glossary_terms[DATAZONE_GLOSSARY_NAME][biz_domain])

    # 5. 자산 리비전 생성 API 호출
    print(f"자산 ID {asset_id}의 메타데이터 업데이트 중...")

    response = datazone_client.create_asset_revision(
        identifier=asset_id,
        domainIdentifier=DATAZONE_DOMAIN_ID,
        formsInput=[readme_form, metadata_form],
        glossaryTerms=glossary_term_ids,
        name=display_name,
        description=description
    )

    print(f"자산 메타데이터 업데이트 완료: {display_name}")
    return response

def main():
    """
    메인 실행 함수 - 명령줄 인자를 파싱하고 자산 메타데이터를 업데이트합니다.
    """
    try:
        # 명령줄 인자 파싱
        args = parse_arguments()

        # 필수 인자 확인
        if not args.asset_id:
            print("오류: --asset-id 인자가 필요합니다.")
            sys.exit(1)

        asset_id = args.asset_id
        print(f"Tableau 대시보드 자산 메타데이터 업데이트 시작 (자산 ID: {asset_id})...")

        # 대시보드 정보 가져오기
        dashboard_info = None

        # 대시보드 ID가 제공되면 실제 Tableau DB에서 조회
        if args.dashboard_id:
            dashboard_id = args.dashboard_id
            print(f"대시보드 ID {dashboard_id}에 대한 정보 조회 중...")
            dashboard_info = get_dashboard_info(dashboard_id)

            if not dashboard_info:
                print(f"오류: 대시보드 ID {dashboard_id}에 대한 정보를 찾을 수 없습니다.")
                sys.exit(1)
        else:
            # 테스트용 예시 데이터 사용
            print("대시보드 ID가 제공되지 않아 예시 데이터를 사용합니다.")
            dashboard_info = {
                'site_name': 'sales',
                'id': '1',
                'name': 'Sales Performance Dashboard',
                'description': '목표 대비 판매 실적을 분석하고 매장별, 제품별, 기간별 성과를 시각화하는 대시보드',
                'owner': '영업팀_홍길동',
                'size': '3.1',
                'last_updated_date': '2025-04-11 08:30:00',
                # 비즈니스 메타데이터 
                'status_notes': '매일 새벽 3시에 전날 판매 데이터로 자동 업데이트됨',
                'biz_domain': 'sales',
                'tags': 'performance, targets, analytics, revenue'
            }

        # 메타데이터 업데이트 실행
        response = update_asset_metadata(asset_id, dashboard_info)
        return response

    except Exception as e:
        print(f"자산 메타데이터 업데이트 중 오류 발생: {str(e)}")
        import traceback
        traceback.print_exc()
        return None

if __name__ == "__main__":
    main()
                                                                                                                                                          360,0-1       Bot