import sys
import os
import hashlib
import shutil
import threading
import time
from datetime import datetime, timedelta

from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget,
    QFileDialog, QProgressBar, QLabel, QTextEdit, QHBoxLayout,
    QLineEdit, QFrame
)
from PyQt5.QtCore import Qt, pyqtSignal, QObject, QTimer
from PyQt5.QtGui import QIcon
import qtawesome as qta

# --- 전역 상수 ---
LOG_FILE = 'backup_log.txt'
HISTORY_FILE = 'backup_history.txt'
MAX_BACKUPS = 5  # 자동 정리: 최대 백업본 개수

def file_hash(path):
    """파일의 SHA256 해시값 계산 (중복 체크용)"""
    h = hashlib.sha256()
    with open(path, 'rb') as f:
        while chunk := f.read(8192):
            h.update(chunk)
    return h.hexdigest()

class BackupWorker(QObject):
    """백업 작업을 처리하는 백그라운드 스레드"""
    progress = pyqtSignal(int)
    file_copied = pyqtSignal(str)
    status = pyqtSignal(str)
    finished = pyqtSignal()
    stopped = pyqtSignal()

    def __init__(self, src, dst, pause_event, stop_event):
        super().__init__()
        self.src = src
        self.dst = dst
        self.pause_event = pause_event
        self.stop_event = stop_event

    def run(self):
        try:
            self.status.emit("파일 리스트 수집 중...")
            file_list = []
            for root, dirs, files in os.walk(self.src):
                for name in files:
                    full = os.path.join(root, name)
                    rel = os.path.relpath(full, self.src)
                    file_list.append(rel)
            
            if not file_list:
                self.status.emit("원본 폴더에 백업할 파일이 없습니다.")
                self.finished.emit()
                return

            total = len(file_list)
            copied = 0

            # 히스토리 불러오기 (증분 체크용)
            old_hashes = self._load_backup_history()

            # 새 백업본 폴더 (타임스탬프 기반)
            backup_name = datetime.now().strftime('%Y%m%d_%H%M%S')
            backup_path = os.path.join(self.dst, backup_name)
            os.makedirs(backup_path, exist_ok=True)
            self.status.emit(f"백업 폴더 생성: {backup_path}")

            new_hashes = {}

            for idx, rel_path in enumerate(file_list):
                if self.stop_event.is_set():
                    self.status.emit("백업 중지됨")
                    self.stopped.emit()
                    return

                self.pause_event.wait()  # 일시정지 이벤트 대기

                src_file = os.path.join(self.src, rel_path)
                dst_file = os.path.join(backup_path, rel_path)

                # 파일 해시로 변경 여부 확인 (증분 백업)
                hash_val = file_hash(src_file)
                new_hashes[rel_path] = hash_val
                if rel_path in old_hashes and old_hashes[rel_path] == hash_val:
                    self.status.emit(f"[SKIP] {rel_path} (변경 없음)")
                else:
                    os.makedirs(os.path.dirname(dst_file), exist_ok=True)
                    shutil.copy2(src_file, dst_file)
                    self.file_copied.emit(rel_path)
                    self.status.emit(f"[COPY] {rel_path}")

                copied += 1
                progress = int(copied / total * 100)
                self.progress.emit(progress)

            self._save_backup_history(new_hashes)
            self.status.emit("백업 완료!")
            self._auto_cleanup()
            self.finished.emit()
        except Exception as e:
            self.status.emit(f"오류 발생: {e}")
            self.finished.emit()

    def _load_backup_history(self):
        if not os.path.exists(HISTORY_FILE):
            return {}
        try:
            with open(HISTORY_FILE, 'r', encoding='utf-8') as f:
                lines = f.readlines()
                return dict(line.strip().split('\t') for line in lines if '\t' in line)
        except Exception:
            return {}

    def _save_backup_history(self, hashes):
        with open(HISTORY_FILE, 'w', encoding='utf-8') as f:
            for rel, hashv in hashes.items():
                f.write(f"{rel}\t{hashv}\n")

    def _auto_cleanup(self):
        """오래된 백업 자동 삭제"""
        try:
            backups = sorted([
                d for d in os.listdir(self.dst)
                if os.path.isdir(os.path.join(self.dst, d))
            ])
            if len(backups) > MAX_BACKUPS:
                self.status.emit(f"최대 백업 개수({MAX_BACKUPS}) 초과. 오래된 백업을 삭제합니다.")
                for b in backups[:-MAX_BACKUPS]:
                    b_path = os.path.join(self.dst, b)
                    shutil.rmtree(b_path)
                    self.status.emit(f"오래된 백업 삭제: {b_path}")
        except Exception as e:
            self.status.emit(f"[정리 오류] {e}")


class MainWindow(QMainWindow):
    """메인 윈도우 UI 및 기능 담당"""
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Pro Backup Tool")
        self.setGeometry(100, 100, 700, 550)

        # --- 상태 변수 ---
        self.src_dir = ""
        self.dst_dir = ""
        self.backup_thread = None
        self.worker = None
        self.pause_event = threading.Event()
        self.pause_event.set()
        self.stop_event = threading.Event()
        self.timer = QTimer()
        self.timer.timeout.connect(self.scheduled_backup)
        self.scheduled = False

        # --- UI 초기화 ---
        self.init_ui()
        self.apply_stylesheet()
        self.reset_ui_state()

    def init_ui(self):
        """UI 위젯 생성 및 레이아웃 구성"""
        main_layout = QVBoxLayout()
        main_layout.setContentsMargins(20, 20, 20, 20)
        main_layout.setSpacing(15)

        # 경로 선택 섹션
        path_layout = QVBoxLayout()
        path_layout.setSpacing(10)
        
        src_layout = QHBoxLayout()
        self.line_src = QLineEdit("원본 폴더를 선택하세요.")
        self.line_src.setReadOnly(True)
        btn_src = QPushButton("찾아보기")
        btn_src.clicked.connect(self.select_src)
        src_layout.addWidget(QLabel("원본:"))
        src_layout.addWidget(self.line_src)
        src_layout.addWidget(btn_src)

        dst_layout = QHBoxLayout()
        self.line_dst = QLineEdit("백업을 저장할 폴더를 선택하세요.")
        self.line_dst.setReadOnly(True)
        btn_dst = QPushButton("찾아보기")
        btn_dst.clicked.connect(self.select_dst)
        dst_layout.addWidget(QLabel("대상:"))
        dst_layout.addWidget(self.line_dst)
        dst_layout.addWidget(btn_dst)
        
        path_layout.addLayout(src_layout)
        path_layout.addLayout(dst_layout)

        # 컨트롤 버튼 섹션
        control_layout = QHBoxLayout()
        self.btn_start = QPushButton(qta.icon('fa5s.play-circle', color='lightblue'), " 백업 시작")
        self.btn_pause = QPushButton(qta.icon('fa5s.pause-circle', color='lightyellow'), " 일시정지")
        self.btn_stop = QPushButton(qta.icon('fa5s.stop-circle', color='#ff7f7f'), " 중지")
        
        self.btn_start.clicked.connect(self.start_backup)
        self.btn_pause.clicked.connect(self.pause_resume)
        self.btn_stop.clicked.connect(self.stop_backup)

        control_layout.addWidget(self.btn_start)
        control_layout.addWidget(self.btn_pause)
        control_layout.addWidget(self.btn_stop)

        # 스케줄 버튼
        schedule_layout = QHBoxLayout()
        self.btn_schedule = QPushButton(qta.icon('fa5s.clock', color='lightgray'), " 자동 백업 (5분)")
        self.btn_schedule.setCheckable(True)
        self.btn_schedule.clicked.connect(self.toggle_schedule)
        schedule_layout.addStretch(1)
        schedule_layout.addWidget(self.btn_schedule)
        
        # 진행률 및 상태 표시
        self.progress = QProgressBar()
        self.label_file = QLabel("대기 중...")
        self.label_file.setObjectName("StatusLabel")
        self.text_status = QTextEdit()
        self.text_status.setReadOnly(True)

        # 구분선
        separator1 = QFrame()
        separator1.setFrameShape(QFrame.HLine)
        separator1.setFrameShadow(QFrame.Sunken)
        
        separator2 = QFrame()
        separator2.setFrameShape(QFrame.HLine)
        separator2.setFrameShadow(QFrame.Sunken)

        # 레이아웃에 위젯 추가
        main_layout.addLayout(path_layout)
        main_layout.addWidget(separator1)
        main_layout.addLayout(control_layout)
        main_layout.addLayout(schedule_layout)
        main_layout.addWidget(self.label_file)
        main_layout.addWidget(self.progress)
        main_layout.addWidget(separator2)
        main_layout.addWidget(QLabel("상태 로그:"))
        main_layout.addWidget(self.text_status, 1) # 로그창이 남은 공간을 모두 차지하도록 stretch 추가

        central_widget = QWidget()
        central_widget.setLayout(main_layout)
        self.setCentralWidget(central_widget)

    def apply_stylesheet(self):
        """QSS 스타일시트 적용"""
        qss = """
            QMainWindow {
                background-color: #2c3e50;
            }
            QWidget {
                color: #ecf0f1;
                font-family: '맑은 고딕', 'Malgun Gothic', sans-serif;
                font-size: 10pt;
            }
            QLabel {
                color: #bdc3c7;
                font-size: 9pt;
            }
            QLabel#StatusLabel {
                color: #ecf0f1;
                font-size: 8pt;
                padding-left: 3px;
            }
            QLineEdit {
                background-color: #34495e;
                border: 1px solid #2c3e50;
                padding: 5px;
                border-radius: 4px;
                color: #ecf0f1;
            }
            QPushButton {
                background-color: #3498db;
                color: white;
                border: none;
                padding: 8px 16px;
                border-radius: 4px;
                font-weight: bold;
            }
            QPushButton:hover {
                background-color: #2980b9;
            }
            QPushButton:pressed {
                background-color: #1f618d;
            }
            QPushButton:disabled {
                background-color: #566573;
                color: #95a5a6;
            }
            QPushButton:checked {
                background-color: #27ae60;
            }
            QTextEdit {
                background-color: #202b36;
                border: 1px solid #34495e;
                border-radius: 4px;
                color: #bdc3c7;
            }
            QProgressBar {
                border: 1px solid #34495e;
                border-radius: 5px;
                text-align: center;
                background-color: #34495e;
                color: white;
            }
            QProgressBar::chunk {
                background-color: #3498db;
                border-radius: 4px;
            }
            QFrame[frameShape="HLine"] {
                color: #34495e;
            }
        """
        self.setStyleSheet(qss)

    def select_src(self):
        d = QFileDialog.getExistingDirectory(self, "원본 폴더 선택")
        if d:
            self.src_dir = d
            self.line_src.setText(self.src_dir)
            self.log_status(f"원본 폴더 설정: {self.src_dir}")

    def select_dst(self):
        d = QFileDialog.getExistingDirectory(self, "백업 폴더 선택")
        if d:
            self.dst_dir = d
            self.line_dst.setText(self.dst_dir)
            self.log_status(f"백업 폴더 설정: {self.dst_dir}")

    def start_backup(self):
        if not self.src_dir or not self.dst_dir:
            self.log_status("오류: 원본 및 대상 폴더를 모두 선택해야 합니다.")
            return
        
        self.log_status("백업을 시작합니다...")
        self.progress.setValue(0)
        self.stop_event.clear()
        self.pause_event.set()

        self.set_ui_for_backup(True)

        self.worker = BackupWorker(self.src_dir, self.dst_dir, self.pause_event, self.stop_event)
        self.worker.progress.connect(self.progress.setValue)
        self.worker.file_copied.connect(lambda f: self.label_file.setText(f"복사 중: {f}"))
        self.worker.status.connect(self.log_status)
        self.worker.finished.connect(self.backup_done)
        self.worker.stopped.connect(self.backup_stopped)

        self.backup_thread = threading.Thread(target=self.worker.run, daemon=True)
        self.backup_thread.start()

    def pause_resume(self):
        if not self.worker: return
        if self.pause_event.is_set():
            self.pause_event.clear()
            self.btn_pause.setText(" 재개")
            self.btn_pause.setIcon(qta.icon('fa5s.play-circle', color='lightgreen'))
            self.log_status("백업이 일시정지되었습니다.")
        else:
            self.pause_event.set()
            self.btn_pause.setText(" 일시정지")
            self.btn_pause.setIcon(qta.icon('fa5s.pause-circle', color='lightyellow'))
            self.log_status("백업을 재개합니다.")

    def stop_backup(self):
        if self.worker:
            self.stop_event.set()
            self.pause_event.set() # 일시정지 상태 해제하여 스레드 즉시 종료
            self.log_status("백업 중지를 요청했습니다.")

    def backup_done(self):
        self.log_status("백업이 성공적으로 완료되었습니다.")
        self.reset_ui_state()

    def backup_stopped(self):
        self.log_status("백업이 사용자에 의해 중지되었습니다.")
        self.reset_ui_state()
        
    def set_ui_for_backup(self, is_running):
        self.btn_start.setEnabled(not is_running)
        self.btn_pause.setEnabled(is_running)
        self.btn_stop.setEnabled(is_running)

    def reset_ui_state(self):
        self.worker = None
        self.set_ui_for_backup(False)
        self.btn_pause.setText(" 일시정지")
        self.btn_pause.setIcon(qta.icon('fa5s.pause-circle', color='lightyellow'))
        self.label_file.setText("대기 중...")
        self.progress.setValue(0)

    def log_status(self, msg):
        now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        line = f"[{now}] {msg}"
        self.text_status.append(line)
        self.text_status.verticalScrollBar().setValue(self.text_status.verticalScrollBar().maximum())
        
        # 로그 파일 저장
        try:
            with open(LOG_FILE, 'a', encoding='utf-8') as f:
                f.write(line + '\n')
        except Exception as e:
            # UI에 파일 쓰기 오류 표시
            error_line = f"[{now}] [CRITICAL] 로그 파일 쓰기 실패: {e}"
            self.text_status.append(error_line)


    def toggle_schedule(self):
        if self.btn_schedule.isChecked():
            self.timer.start(5 * 60 * 1000)  # 5분
            self.scheduled = True
            self.log_status("자동 백업 스케줄이 활성화되었습니다 (5분 간격).")
        else:
            self.timer.stop()
            self.scheduled = False
            self.log_status("자동 백업 스케줄이 비활성화되었습니다.")

    def scheduled_backup(self):
        if self.worker is None:
            self.log_status("스케줄에 따라 자동 백업을 시작합니다.")
            self.start_backup()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = MainWindow()
    win.show()
    sys.exit(app.exec_())