import sys
import os
import hashlib
import shutil
import threading
from datetime import datetime

from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget,
    QFileDialog, QProgressBar, QLabel, QTextEdit, QHBoxLayout, QFrame, QSizePolicy, QStyle
)
from PyQt5.QtCore import Qt, pyqtSignal, QObject, QTimer, QSize
from PyQt5.QtGui import QFont, QIcon, QColor, QPalette

LOG_FILE = 'backup_log.txt'
HISTORY_FILE = 'backup_history.txt'
MAX_BACKUPS = 5

def file_hash(path):
    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)
            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):
                src_file = os.path.join(self.src, rel_path)
                dst_file = os.path.join(backup_path, rel_path)

                if self.stop_event.is_set():
                    self.status.emit("백업 중지됨")
                    self.stopped.emit()
                    return

                self.pause_event.wait()

                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:
            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:
                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 ModernButton(QPushButton):
    def __init__(self, text, icon=None):
        super().__init__(text)
        self.setCursor(Qt.PointingHandCursor)
        self.setMinimumHeight(38)
        self.setFont(QFont("Segoe UI", 11, QFont.Bold))
        if icon:
            self.setIcon(icon)
            self.setIconSize(QSize(20, 20))
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("INCREMENTAL BACKUP")
        self.setFixedSize(580, 430)
        self.setStyleSheet(MODERN_QSS)

        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

        # ICONS
        st = QApplication.style()
        icon_start = st.standardIcon(QStyle.SP_MediaPlay)
        icon_pause = st.standardIcon(QStyle.SP_MediaPause)
        icon_stop = st.standardIcon(QStyle.SP_MediaStop)
        icon_folder = st.standardIcon(QStyle.SP_DirOpenIcon)
        icon_schedule = st.standardIcon(QStyle.SP_BrowserReload)

        # UI
        outer = QVBoxLayout()
        outer.setContentsMargins(36, 24, 36, 24)
        outer.setSpacing(18)

        # Top Title
        title = QLabel("INCREMENTAL BACKUP TOOL")
        title.setFont(QFont("Segoe UI", 16, QFont.Bold))
        title.setStyleSheet("color: #224CA4; letter-spacing: 2px; margin-bottom: 8px;")
        title.setAlignment(Qt.AlignHCenter)
        outer.addWidget(title)

        # Source & Dest select
        h_select = QHBoxLayout()
        h_select.setSpacing(10)

        self.btn_src = ModernButton("원본 폴더", icon_folder)
        self.btn_dst = ModernButton("백업 폴더", icon_folder)
        self.lbl_src = QLabel("⎆ 폴더 선택")
        self.lbl_dst = QLabel("⎆ 폴더 선택")
        for lbl in [self.lbl_src, self.lbl_dst]:
            lbl.setFont(QFont("Consolas", 10))
            lbl.setStyleSheet("color: #666; padding-left:10px;")
        h_select.addWidget(self.btn_src)
        h_select.addWidget(self.lbl_src)
        h_select.addSpacing(12)
        h_select.addWidget(self.btn_dst)
        h_select.addWidget(self.lbl_dst)
        outer.addLayout(h_select)

        # Progress bar & current file
        self.progress = QProgressBar()
        self.progress.setTextVisible(True)
        self.progress.setFixedHeight(22)
        self.progress.setStyleSheet(
            "QProgressBar { background: #E2E8F5; border-radius: 8px; color: #224CA4; font-weight: bold; }"
            "QProgressBar::chunk { background: #224CA4; border-radius: 8px; }"
        )
        self.label_file = QLabel("")
        self.label_file.setFont(QFont("Consolas", 11, QFont.Bold))
        self.label_file.setStyleSheet("color: #224CA4;")
        self.label_file.setAlignment(Qt.AlignCenter)
        outer.addWidget(self.progress)
        outer.addWidget(self.label_file)

        # Buttons
        btn_line = QHBoxLayout()
        btn_line.setSpacing(12)
        self.btn_start = ModernButton("시작", icon_start)
        self.btn_pause = ModernButton("일시정지", icon_pause)
        self.btn_stop = ModernButton("중지", icon_stop)
        self.btn_schedule = ModernButton("스케줄", icon_schedule)
        btn_line.addWidget(self.btn_start)
        btn_line.addWidget(self.btn_pause)
        btn_line.addWidget(self.btn_stop)
        btn_line.addWidget(self.btn_schedule)
        outer.addLayout(btn_line)

        # Log frame
        self.text_status = QTextEdit()
        self.text_status.setReadOnly(True)
        self.text_status.setFont(QFont("Consolas", 10))
        self.text_status.setStyleSheet(
            "background:#192139;color:#a0c7ff;border-radius:10px;padding:8px 8px 8px 8px;")
        outer.addWidget(self.text_status, 2)

        central = QWidget()
        central.setLayout(outer)
        self.setCentralWidget(central)

        # 시그널 연결
        self.btn_src.clicked.connect(self.select_src)
        self.btn_dst.clicked.connect(self.select_dst)
        self.btn_start.clicked.connect(self.start_backup)
        self.btn_pause.clicked.connect(self.pause_resume)
        self.btn_stop.clicked.connect(self.stop_backup)
        self.btn_schedule.clicked.connect(self.toggle_schedule)

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

    def select_dst(self):
        d = QFileDialog.getExistingDirectory(self, "백업 폴더 선택")
        if d:
            self.dst_dir = d
            self.lbl_dst.setText(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.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(self.label_file.setText)
        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.log_status("일시정지 중...")
        else:
            self.pause_event.set()
            self.btn_pause.setText("일시정지")
            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.worker = None

    def backup_stopped(self):
        self.log_status("백업이 중지되었습니다.")
        self.worker = None

    def log_status(self, msg):
        now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        line = f"[{now}] {msg}"
        self.text_status.append(line)
        with open(LOG_FILE, 'a', encoding='utf-8') as f:
            f.write(line + '\n')

    def toggle_schedule(self):
        if self.scheduled:
            self.timer.stop()
            self.scheduled = False
            self.btn_schedule.setText("스케줄")
            self.log_status("스케줄링 중지")
        else:
            self.timer.start(5 * 60 * 1000)
            self.scheduled = True
            self.btn_schedule.setText("스케줄 해제")
            self.log_status("5분마다 자동 백업 시작")

    def scheduled_backup(self):
        if not self.worker:
            self.start_backup()

MODERN_QSS = """
QWidget {
    background: #F6F8FC;
}
QPushButton {
    background: #224CA4;
    color: white;
    border: none;
    border-radius: 10px;
    padding: 9px 0px 8px 0px;
    font-size: 13px;
    letter-spacing: 0.5px;
    box-shadow: 0 3px 16px #b2c3ee44;
    transition: background 0.1s;
}
QPushButton:hover {
    background: #3963C2;
}
QPushButton:pressed {
    background: #182C5E;
}
QLabel {
    background: transparent;
}
QProgressBar {
    font-size: 12px;
}
"""

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