from DrissionPage import ChromiumPage, ChromiumOptions
from selectolax.parser import HTMLParser
import time

class VOCScraper:
    def __init__(self):
        self.page = None
        # 상세 수집 대상 부서 키워드 설정
        self.target_depts = ['차량', '검수'] 

    def start_browser(self):
        """브라우저(Chromium) 실행 및 초기 설정"""
        if self.page: return True
        co = ChromiumOptions()
        
        # 1. 헤드리스 모드 해제 (디버깅 편의를 위해 일단 화면 출력)
        co.headless(False)
        co.auto_port()

        # 2. 브라우저 보안/자동화 감지 우회 설정
        # co.set_argument('--ignore-certificate-errors') # 인증서 에러 무시
        # co.set_argument('--no-sandbox')
        co.set_argument('--disable-gpu')
        
        # 3. 브라우저 불필요한 팝업 및 기능 비활성화
        co.set_pref('credentials_enable_service', False) # 비밀번호 저장 제안 끄기
        co.set_pref('profile.password_manager_enabled', False)
        
        # '자동제어 중' 알림바 제거
        co.set_argument('--disable-infobars') 
        
        # 콘솔 로그 레벨 조정 (에러만 출력)
        co.set_argument('--log-level=3')

        co.mute(True) # 오디오 음소거
        try:
            self.page = ChromiumPage(co)
            return True
        except Exception as e:
            print(f"브라우저 시작 실패: {e}")
            return False

    def is_logged_in(self):
        """현재 로그인 상태인지 확인 (특정 요소 존재 여부로 판단)"""
        if not self.page: return False
        try:
            return bool(self.page.ele("css:td#m_td2", timeout=2))
        except:
            return False

    def login(self, uid, upw):
        """관리자 페이지 로그인 시도"""
        if not self.page: self.start_browser()
        try:
            print(f">>> [로그인] 계정 {uid} 접속 중...")
            self.page.get("https://www.humetro.busan.kr/voc/admin/login_admin.jsp")
            
            # 입력 폼 대기 및 입력
            uid_field = self.page.ele("css:input[name='userID']", timeout=10)
            if uid_field:
                uid_field.input(uid)
                self.page.ele("css:input[name='password']").input(upw)
                self.page.ele("css:div.btn").click()
                
                # 로그인 성공 여부 확인
                if self.page.ele("css:td#m_td2", timeout=20):
                    print("로그인 성공!")
                    time.sleep(1)
                    return True
            return False
        except Exception as e:
            print(f"로그인 중 오류 발생: {e}")
            return False

    def _check_data_exists(self):
        """현재 페이지에 데이터 행(리스트)이 실제 존재하는지 확인"""
        try:
            html = self.page.html
            tree = HTMLParser(html)
            rows = tree.css("tr")
            for row in rows:
                bg = row.attributes.get('bgcolor', '').lower()
                # 흰색 배경(#ffffff) 행이 실제 데이터 행임
                if bg == '#ffffff' and len(row.css('td')) > 5:
                    return True
            return False
        except:
            return False

    def _navigate_to_list_via_physical_click(self):
        """메뉴 호버 및 클릭을 통해 전체 VOC 목록으로 이동"""
        try:
            menu_td = self.page.ele("css:td#m_td2")
            if not menu_td: return False
            
            # 1. 메뉴 호버링으로 서브메뉴 띄우기
            menu_td.hover()
            time.sleep(0.5)
            
            # 2. '전체VOC보기' 링크 클릭
            target_link = self.page.ele("text:전체VOC보기")
            if target_link:
                target_link.click() 
                time.sleep(2) 
                
                # 혹시 데이터가 안 보이면 검색 버튼 명시적으로 클릭
                if not self._check_data_exists():
                    search_btn = self.page.ele("css:img[alt='검색']")
                    if search_btn:
                        search_btn.click()
                        time.sleep(2)
                return self._check_data_exists()
            return False
        except Exception as e:
            print(f"목록 이동 중 오류: {e}")
            return False

    def fetch_list_pages(self, max_pages_limit=0, keywords=None, target_depts=None):
        """VOC 목록 페이지를 순회하며 데이터 수집"""
        self.stop_requested = False
        all_results = []
        
        # 필터링 조건 설정
        keywords = keywords if keywords else []
        target_depts = target_depts if target_depts else self.target_depts

        try:
            # 0. 목록 페이지인지 확인하고 아니면 이동
            if "act=searchList" not in self.page.url or not self._check_data_exists():
                if not self._navigate_to_list_via_physical_click():
                    print("목록 페이지 이동 실패. 수집을 중단합니다.")
                    return {"status": "error", "msg": "목록 페이지 접근 실패"}

            # 1. 페이지네이션 정보 확인
            total_cnt_ele = self.page.ele("text:page )", timeout=2)
            max_pages = 1
            if total_cnt_ele:
                # 예: ( 1 page / 7 page )
                txt = total_cnt_ele.text
                if "/" in txt:
                    try:
                        max_pages = int(txt.split("/")[1].replace("page", "").replace(")", "").strip())
                    except: pass
            
            # 수집 한도 설정 (인자 우선, 없으면 self.settings 등 참조해야 하나, 
            # 여기선 인자가 0이면 settings가 있을 경우 참조하는 식으로 구성)
            # 그러나 self.settings 직접 참조가 없으므로 인자에 의존하거나 동적 계산
            if max_pages_limit > 0:
                if max_pages > max_pages_limit:
                    max_pages = max_pages_limit
            
            print(f">>> 총 {max_pages} 페이지 수집 예정")

            # 2. 페이지 순회
            for page_num in range(1, max_pages + 1):
                if self.stop_requested: break
                
                print(f">>> [목록] {page_num} 페이지 수집 중...")
                if page_num > 1:
                    # 페이지 번호 클릭 ([2], [3]...)
                    # 자바스크립트 함수 호출이므로 text로 찾아서 클릭
                    page_btn = self.page.ele(f"text:[{page_num}]")
                    if page_btn:
                        page_btn.click()
                        time.sleep(1.5)
                    else: break

                # 현재 페이지 HTML 파싱
                tree = HTMLParser(self.page.html)
                
                # 배경색이 흰색인 행만 데이터 행으로 간주 (헤더 제외)
                rows = tree.css("tr[bgcolor='#ffffff']")

                for row in rows:
                    if self.stop_requested: break
                    cols = row.css("td")
                    if len(cols) < 7: continue

                    # [0]접수번호, [1]채널, [2]제목, [3]고객명, [4]부서, [5]공개, [6]상태
                    voc_id = cols[0].text(strip=True)
                    channel = cols[1].text(strip=True)
                    
                    # 제목: a 태그의 title 속성(전체 제목) 우선, 없으면 텍스트
                    title_a = cols[2].css_first("a")
                    title = title_a.attributes.get("title", "") if title_a else ""
                    if not title and title_a: title = title_a.text(strip=True)
                    
                    writer = cols[3].text(strip=True)
                    dept = cols[4].text(strip=True)
                    
                    is_public_txt = cols[5].text(strip=True)
                    is_public = 1 if is_public_txt == '○' or is_public_txt == 'O' else 0
                    
                    status = cols[6].text(strip=True)

                    post_info = {
                        "id": voc_id,
                        "title": title,
                        "writer": writer,
                        "department": dept,
                        "is_public": is_public,
                        "status": status,
                        "channel": channel,
                        "is_related": 0
                    }

                    # 타겟 부서/키워드 필터링
                    is_target = False
                    
                    # 1. 키워드 매칭
                    for kw in keywords:
                        if kw in title:
                            is_target = True
                            break
                    
                    # 2. 부서 매칭
                    if not is_target:
                        for target in target_depts:
                            if target in dept:
                                is_target = True
                                break
                    
                    post_info["is_related"] = 1 if is_target else 0

                    # 상세 수집 대상이면 (관심 게시글이거나 공개글이면)
                    # *여기서는 '상세 수집'을 별도로 하지 않고 목록 정보만 우선 저장*
                    # 상세 정보는 update_detail 로직이나 필요시점에 호출
                    # 단, 본문 내용 검색이 필요하다면 여기서 들어가야 함.
                    # 현재 요구사항은 알림이 중요하므로, 관련성이 있으면 상세를 긁는게 맞음.
                    
                    # 리스트 수집 시 상세 내용도 긁고 싶다면:
                    if is_target: # 관심 대상인 경우 상세까지 긁어서 정확도 높임
                        detail = self.fetch_detail_content(voc_id)
                        if detail:
                            # 상세에서 가져온 더 정확한 정보로 덮어쓰기
                            post_info.update(detail)
                            
                    all_results.append(post_info)

            return {"status": "success", "data": all_results}

        except Exception as e:
            print(f"목록 수집 중 에러: {e}")
            return {"status": "error", "msg": str(e)}

    def fetch_detail_content(self, voc_id):
        """특정 VOC의 모든 상세 데이터를 추출 (제목, 역명, 질의내용, 답변, 첨부파일 등)"""
        try:
            # 먼저 목록에서 해당 링크 찾기
            target_link = self.page.ele(f"css:a[href*='vocCode={voc_id}']", timeout=2)
            
            if not target_link:
                print(f"   -> [상세수집 스킵] 목록에서 {voc_id} 링크를 찾을 수 없음 (비공개이거나 페이지 넘어감)")
                return None

            # 비공개 글 판단: 링크 텍스트나 주변 요소로 확인 가능하면 좋으나,
            # 클릭 후 알림창(비공개입니다)이 뜨는지 확인하는 게 확실함.
            # 하지만 사용자 요청은 "열람하지마"이므로, 목록 수집 단계에서 이미 걸러져서 들어와야 함.
            # 여기서는 클릭을 시도함.
            
            target_link.click()
            
            # 상세 페이지 로딩 대기 (타임아웃 15초로 연장)
            # 성공 시 '질의내용' 텍스트 존재, 실패 시(비공개 등) 알림창 혹은 목록 유지
            if self.page.ele("text:질의내용", timeout=15):
                tree = HTMLParser(self.page.html)
                data = {
                    "id": voc_id,
                    "title": "", "station": "", "content": "", "attachment": "",
                    "answer": "", "channel": "", "date": "", "voc_type": "",
                    "response_type": "", "summary": ""
                }
                
                # 1. 제목 추출 (상세 페이지 상단)
                title_a = tree.css_first("td.title, td[style*='font-weight:bold']") 
                if title_a: data["title"] = title_a.text(strip=True)

                # 2. 라벨 기반 데이터 추출 (bgcolor로 구분된 항목명 탐색)
                labels = tree.css("td[bgcolor='#E0EDEF']")
                for label in labels:
                    key = label.text(strip=True)
                    
                    parent = label.parent
                    if not parent: continue
                    tds = parent.css("td")
                    
                    idx = -1
                    for i, td in enumerate(tds):
                        if td == label:
                            idx = i
                            break
                    
                    if idx != -1 and idx + 1 < len(tds):
                        val_node = tds[idx+1]
                        val_txt = val_node.text(separator="\n", strip=True)

                        if "제목" in key and not data["title"]: data["title"] = val_txt
                        elif "역명" in key: data["station"] = val_txt
                        elif "질의내용" in key: data["content"] = val_txt
                        elif "첨부파일" in key: data["attachment"] = val_txt
                        elif "답변내용" in key: data["answer"] = val_txt
                        elif "접수채널" in key: data["channel"] = val_txt
                        elif "등록일자" in key: data["date"] = val_txt
                        elif "VOC유형" in key: data["voc_type"] = val_txt
                        elif "요약" in key: data["summary"] = val_txt
                        
                        # 응답구분: 체크박스 확인
                        elif "응답구분" in key:
                            checked_input = val_node.css_first("input[checked]")
                            if checked_input:
                                val = checked_input.attributes.get('value', '6')
                                mapping = {'1':'게시판', '2':'우편', '3':'팩스', '4':'전화', '5':'E-mail', '6':'기타'}
                                data["response_type"] = mapping.get(val, "기타")
                            else:
                                data["response_type"] = ""

                # 수집 완료 후 목록으로 복귀
                if "vocCode=" in self.page.url:
                    self.page.back()
                    time.sleep(0.8)
                return data
            else:
                # 로딩 실패 (타임아웃) 또는 비공개 글 등 다른 상황
                # 현재 URL이 여전히 목록인지, 아니면 상세페이지인지 확인
                print(f"   -> [오류] {voc_id} 상세 페이지 로딩 실패 (Timeout 15s). URL: {self.page.url}")
                
                # 만약 상세 페이지 URL로 진입했다면 뒤로가기
                if "vocCode=" in self.page.url:
                    self.page.back()
                return None

        except Exception as e:
            print(f"   -> [예외] {voc_id} 상세 수집 중 발생: {e}")
            if "vocCode=" in self.page.url:
                try: self.page.back()
                except: pass
            return None

    def close(self):
        """브라우저 종료"""
        if self.page:
            self.page.quit()
