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,
    QFrame, QGridLayout, QSpacerItem, QSizePolicy
)
from PyQt5.QtCore import Qt, pyqtSignal, QObject, QTimer, QPropertyAnimation, QEasingCurve
from PyQt5.QtGui import QPalette, QColor, QFont, QPixmap, QPainter, QLinearGradient, QBrush

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

# 현대적인 색상 팔레트
COLORS = {
    'bg_dark': '#181a1b',
    'bg_card': '#23272e',
    'primary': '#2563eb',
    'primary_hover': '#1d4ed8',
    'danger': '#ef4444',
    'text_primary': '#f3f4f6',
    'text_dark': '#23272e',
    'border': '#444',
    'button_bg': '#f3f4f6',
    'button_hover': '#e0e7ef',
}

class ModernButton(QPushButton):
    """현대적인 스타일의 버튼"""
    def __init__(self, text, primary=False, danger=False):
        super().__init__(text)
        self.primary = primary
        self.danger = danger
        self.setMinimumHeight(32)
        self.setup_style()
        
    def setup_style(self):
        if self.primary:
            bg_color = COLORS['primary']
            hover_color = COLORS['primary_hover']
            text_color = '#fff'
        elif self.danger:
            bg_color = COLORS['danger']
            hover_color = '#dc2626'
            text_color = '#fff'
        else:
            bg_color = COLORS['button_bg']
            hover_color = COLORS['button_hover']
            text_color = COLORS['text_dark']
            
        self.setStyleSheet(f"""
            QPushButton {{
                background: {bg_color};
                color: {text_color};
                border-radius: 6px;
                font-size: 14px;
                font-weight: 400;
                padding: 8px 16px;
                border: 1px solid {COLORS['border']};
            }}
            QPushButton:hover {{
                background: {hover_color};
            }}
            QPushButton:pressed {{
                background: {bg_color};
            }}
            QPushButton:disabled {{
                background: #e5e7eb;
                color: #a1a1aa;
            }}
        """)
        self.setCursor(Qt.PointingHandCursor)

class ModernProgressBar(QProgressBar):
    """현대적인 스타일의 프로그레스 바"""
    def __init__(self):
        super().__init__()
        self.setMinimumHeight(16)
        self.setStyleSheet(f"""
            QProgressBar {{
                border: 1px solid {COLORS['border']};
                border-radius: 6px;
                background: {COLORS['bg_card']};
                text-align: center;
                font-size: 13px;
                color: {COLORS['text_primary']};
                min-height: 16px;
            }}
            QProgressBar::chunk {{
                background: {COLORS['primary']};
                border-radius: 5px;
                margin: 1px;
            }}
        """)

class ModernTextEdit(QTextEdit):
    """현대적인 스타일의 텍스트 에디터"""
    def __init__(self):
        super().__init__()
        self.setStyleSheet(f"""
            QTextEdit {{
                background: transparent;
                border: 1px solid {COLORS['border']};
                border-radius: 6px;
                color: {COLORS['text_primary']};
                font-size: 14px;
                font-weight: 400;
                padding: 8px;
            }}
            QTextEdit:focus {{
                border: 1.5px solid {COLORS['primary']};
            }}
        """)
        self.setMinimumHeight(48)

class CardFrame(QFrame):
    """카드 스타일의 프레임"""
    def __init__(self):
        super().__init__()
        self.setStyleSheet(f"""
            QFrame {{
                background: {COLORS['bg_card']};
                border: 1px solid {COLORS['border']};
                border-radius: 6px;
                margin: 4px;
                padding: 16px;
            }}
        """)

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)
            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()  # 일시정지 시 block

                # 파일 해시로 변경 여부 확인 (증분/중복)
                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):
        """MAX_BACKUPS 초과시 가장 오래된 백업 삭제"""
        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 MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setup_ui()
        self.setup_style()
        
        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

    def setup_ui(self):
        self.setWindowTitle("Backup Pro - 고급 백업 도구")
        self.setFixedSize(800, 600)
        
        # 메인 위젯과 레이아웃
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        main_layout = QVBoxLayout(central_widget)
        main_layout.setSpacing(16)
        main_layout.setContentsMargins(16, 16, 16, 16)
        
        # 헤더 섹션
        header_card = CardFrame()
        header_layout = QVBoxLayout(header_card)
        header_layout.setSpacing(8)
        header_layout.setContentsMargins(0, 0, 0, 0)
        
        title_label = QLabel("Backup Pro")
        title_label.setStyleSheet(f"color: {COLORS['text_primary']}; font-size: 32px; font-weight: 700;")
        title_label.setMinimumHeight(36)
        title_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        
        subtitle_label = QLabel("고급 증분 백업 시스템")
        subtitle_label.setStyleSheet(f"color: {COLORS['text_primary']}; font-size: 18px; font-weight: 400;")
        subtitle_label.setMinimumHeight(24)
        subtitle_label.setWordWrap(True)
        subtitle_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        
        header_layout.addWidget(title_label)
        header_layout.addWidget(subtitle_label)
        main_layout.addWidget(header_card)
        
        # 폴더 선택 섹션
        folder_card = CardFrame()
        folder_layout = QGridLayout(folder_card)
        folder_layout.setSpacing(12)
        folder_layout.setContentsMargins(0, 0, 0, 0)
        
        folder_title = QLabel("백업 설정")
        folder_title.setStyleSheet(f"color: {COLORS['text_primary']}; font-size: 20px; font-weight: 600;")
        folder_title.setMinimumHeight(28)
        folder_title.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        folder_layout.addWidget(folder_title, 0, 0, 1, 2)
        
        self.btn_src = ModernButton("📁 원본 폴더 선택", primary=True)
        self.btn_dst = ModernButton("📁 백업 폴더 선택", primary=True)
        
        folder_layout.addWidget(self.btn_src, 1, 0)
        folder_layout.addWidget(self.btn_dst, 1, 1)
        
        self.src_label = QLabel("원본 폴더가 선택되지 않음")
        self.dst_label = QLabel("백업 폴더가 선택되지 않음")
        
        for label in [self.src_label, self.dst_label]:
            label.setStyleSheet(f"color: {COLORS['text_primary']}; background: transparent; font-size: 14px; font-weight: 400; padding: 4px; border-radius: 4px;")
            label.setMinimumHeight(20)
            label.setWordWrap(True)
            label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        
        folder_layout.addWidget(self.src_label, 2, 0)
        folder_layout.addWidget(self.dst_label, 2, 1)
        
        main_layout.addWidget(folder_card)
        
        # 제어 버튼 섹션
        control_card = CardFrame()
        control_layout = QVBoxLayout(control_card)
        control_layout.setSpacing(12)
        control_layout.setContentsMargins(0, 0, 0, 0)
        
        control_title = QLabel("백업 제어")
        control_title.setStyleSheet(f"color: {COLORS['text_primary']}; font-size: 20px; font-weight: 600;")
        control_title.setMinimumHeight(28)
        control_title.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        control_layout.addWidget(control_title)
        
        button_layout = QHBoxLayout()
        button_layout.setSpacing(12)
        self.btn_start = ModernButton("▶ 시작", primary=True)
        self.btn_pause = ModernButton("⏸ 일시정지")
        self.btn_stop = ModernButton("⏹ 중지", danger=True)
        self.btn_schedule = ModernButton("⏰ 자동 백업")
        
        for btn in [self.btn_start, self.btn_pause, self.btn_stop, self.btn_schedule]:
            btn.setMinimumHeight(32)
            button_layout.addWidget(btn)
        
        control_layout.addLayout(button_layout)
        main_layout.addWidget(control_card)
        
        # 진행 상황 섹션
        progress_card = CardFrame()
        progress_layout = QVBoxLayout(progress_card)
        progress_layout.setSpacing(8)
        progress_layout.setContentsMargins(0, 0, 0, 0)
        
        progress_title = QLabel("진행 상황")
        progress_title.setStyleSheet(f"color: {COLORS['text_primary']}; font-size: 20px; font-weight: 600;")
        progress_title.setMinimumHeight(28)
        progress_title.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        progress_layout.addWidget(progress_title)
        
        self.progress = ModernProgressBar()
        progress_layout.addWidget(self.progress)
        
        self.label_file = QLabel("대기 중...")
        self.label_file.setStyleSheet(f"color: {COLORS['text_primary']}; background: transparent; font-size: 14px; font-weight: 400; padding: 4px; border-radius: 4px;")
        self.label_file.setMinimumHeight(20)
        self.label_file.setWordWrap(True)
        self.label_file.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        progress_layout.addWidget(self.label_file)
        
        main_layout.addWidget(progress_card)
        
        # 로그 섹션
        log_card = CardFrame()
        log_layout = QVBoxLayout(log_card)
        log_layout.setSpacing(8)
        log_layout.setContentsMargins(0, 0, 0, 0)
        
        log_title = QLabel("백업 로그")
        log_title.setStyleSheet(f"color: {COLORS['text_primary']}; font-size: 20px; font-weight: 600;")
        log_title.setMinimumHeight(28)
        log_title.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        log_layout.addWidget(log_title)
        
        self.text_status = ModernTextEdit()
        self.text_status.setMinimumHeight(80)
        log_layout.addWidget(self.text_status)
        
        main_layout.addWidget(log_card)

    def setup_style(self):
        # 애플리케이션 전체 스타일 설정
        self.setStyleSheet(f"""
            QMainWindow {{
                background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                    stop:0 {COLORS['bg_dark']}, stop:1 #0f0f0f);
            }}
            QWidget {{
                background: transparent;
            }}
        """)
        
        # 팔레트 설정
        palette = self.palette()
        palette.setColor(QPalette.Window, QColor(COLORS['bg_dark']))
        palette.setColor(QPalette.WindowText, QColor(COLORS['text_primary']))
        palette.setColor(QPalette.Base, QColor(COLORS['bg_card']))
        palette.setColor(QPalette.AlternateBase, QColor(COLORS['bg_card']))
        palette.setColor(QPalette.ToolTipBase, QColor(COLORS['bg_card']))
        palette.setColor(QPalette.ToolTipText, QColor(COLORS['text_primary']))
        palette.setColor(QPalette.Text, QColor(COLORS['text_primary']))
        palette.setColor(QPalette.Button, QColor(COLORS['bg_card']))
        palette.setColor(QPalette.ButtonText, QColor(COLORS['text_primary']))
        palette.setColor(QPalette.BrightText, QColor(COLORS['text_primary']))
        palette.setColor(QPalette.Link, QColor(COLORS['primary']))
        palette.setColor(QPalette.Highlight, QColor(COLORS['primary']))
        palette.setColor(QPalette.HighlightedText, QColor(COLORS['text_primary']))
        
        self.setPalette(palette)
        
        # 시그널 연결
        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.src_label.setText(f"원본: {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.dst_label.setText(f"백업: {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.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)  # 5분
            self.scheduled = True
            self.btn_schedule.setText("⏹ 스케줄 중지")
            self.log_status("⏰ 5분마다 자동 백업 시작")

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

if __name__ == "__main__":
    app = QApplication(sys.argv)
    
    # 애플리케이션 전체 스타일 설정
    app.setStyle('Fusion')
    
    win = MainWindow()
    win.show()
    sys.exit(app.exec_())
