"""
Windows 이벤트 뷰어 모니터
시스템 부팅, 로그인, 종료 시간을 감지
"""
import win32evtlog
import win32evtlogutil
import win32con
import win32security
import subprocess
from datetime import datetime, timedelta, date
from typing import Optional, List, Dict


class EventMonitor:
    """Windows 이벤트 로그 모니터링 클래스"""

    # 이벤트 ID 정의
    EVENT_SYSTEM_BOOT = 6005  # 시스템 부팅
    EVENT_SYSTEM_START = 6009  # 시스템 시작
    EVENT_USER_LOGON = 4624   # 사용자 로그인
    EVENT_SYSTEM_SHUTDOWN = 6006  # 시스템 종료
    EVENT_SLEEP_ENTER = 42    # 절전 모드 진입 (Kernel-Power)

    def __init__(self):
        self.server = None  # 로컬 컴퓨터
        self.logtype = "System"  # 시스템 로그

    def get_boot_time_powershell(self) -> Optional[datetime]:
        """
        PowerShell을 사용하여 시스템 부팅 시간 조회
        오늘 부팅한 경우에만 반환
        Returns:
            datetime: 오늘의 부팅 시간, 오늘 부팅 안 했으면 None
        """
        try:
            # PowerShell 명령어 실행
            result = subprocess.run(
                ['powershell', '-Command',
                 '(Get-CimInstance -ClassName Win32_OperatingSystem).LastBootUpTime.ToString("yyyy-MM-dd HH:mm:ss")'],
                capture_output=True,
                text=True,
                timeout=5
            )

            if result.returncode == 0 and result.stdout.strip():
                boot_time_str = result.stdout.strip()
                # "yyyy-MM-dd HH:mm:ss" 형식으로 파싱
                boot_time = datetime.strptime(boot_time_str, "%Y-%m-%d %H:%M:%S")

                # 오늘 부팅한 경우에만 반환
                today = datetime.now().date()
                if boot_time.date() == today:
                    return boot_time
                else:
                    # 오늘 부팅하지 않음 (절전 모드에서 깨어난 경우)
                    print(f"Last boot was on {boot_time.date()}, not today ({today})")
                    return None

        except Exception as e:
            print(f"PowerShell boot time query failed: {e}")

        return None

    def get_today_boot_time(self) -> Optional[datetime]:
        """
        오늘 첫 부팅 시간 조회 (절전 모드 복귀 포함)
        오늘의 가장 빠른 시스템 시작 시간을 반환
        Returns:
            datetime: 첫 부팅/시작 시간, 없으면 None
        """
        today = datetime.now().date()
        earliest_time = None

        # Kernel-General (Event ID 1, 12) - 시스템 시작/재개
        kernel_general_events = self._get_events_by_id(1, limit=50)
        for event in kernel_general_events:
            event_time = event['time']
            if event_time.date() == today:
                if earliest_time is None or event_time < earliest_time:
                    earliest_time = event_time

        # Event ID 12도 확인
        kernel_general_12 = self._get_events_by_id(12, limit=50)
        for event in kernel_general_12:
            event_time = event['time']
            if event_time.date() == today:
                if earliest_time is None or event_time < earliest_time:
                    earliest_time = event_time

        # Kernel-General에서 찾았으면 반환
        if earliest_time:
            return earliest_time

        # 못 찾았으면 전통적인 부팅 이벤트 확인
        # 6005 - 시스템 부팅
        boot_events = self._get_events_by_id(self.EVENT_SYSTEM_BOOT, limit=10)
        for event in boot_events:
            event_time = event['time']
            if event_time.date() == today:
                if earliest_time is None or event_time < earliest_time:
                    earliest_time = event_time

        # 6009 - 시스템 시작
        start_events = self._get_events_by_id(self.EVENT_SYSTEM_START, limit=10)
        for event in start_events:
            event_time = event['time']
            if event_time.date() == today:
                if earliest_time is None or event_time < earliest_time:
                    earliest_time = event_time

        return earliest_time

    def get_last_shutdown_time(self) -> Optional[datetime]:
        """
        마지막 종료 시간 조회
        Returns:
            datetime: 마지막 종료 시간, 없으면 None
        """
        shutdown_events = self._get_events_by_id(self.EVENT_SYSTEM_SHUTDOWN, limit=5)

        if shutdown_events:
            return shutdown_events[0]['time']

        return None

    def get_yesterday_shutdown_time(self) -> Optional[datetime]:
        """
        어제의 종료 시간 조회 (퇴근 시간으로 사용)
        Returns:
            datetime: 어제의 종료 시간, 없으면 None
        """
        yesterday = (datetime.now().date() - timedelta(days=1))
        shutdown_events = self._get_events_by_id(self.EVENT_SYSTEM_SHUTDOWN, limit=20)

        for event in shutdown_events:
            event_time = event['time']
            if event_time.date() == yesterday:
                return event_time

        return None

    def get_shutdown_time_by_date(self, target_date: date) -> Optional[datetime]:
        """
        특정 날짜의 종료 시간 조회 (정상 종료 + 절전 모드)
        Args:
            target_date: 조회할 날짜
        Returns:
            datetime: 해당 날짜의 종료 시간, 없으면 None
        """
        # 정상 종료 이벤트 검색
        shutdown_events = self._get_events_by_id(self.EVENT_SYSTEM_SHUTDOWN, limit=2000)

        # 절전 모드 진입 이벤트도 검색
        sleep_events = self._get_events_by_id(self.EVENT_SLEEP_ENTER, limit=2000)

        # 해당 날짜의 모든 종료/절전 시간을 찾아서 가장 늦은 시간 반환
        matching_events = []

        # 정상 종료 이벤트 확인
        for event in shutdown_events:
            event_time = event['time']
            if event_time.date() == target_date:
                matching_events.append(('shutdown', event_time))

        # 절전 모드 이벤트 확인
        for event in sleep_events:
            event_time = event['time']
            if event_time.date() == target_date:
                matching_events.append(('sleep', event_time))

        if matching_events:
            # 가장 늦은 시간 반환 (타입에 관계없이)
            latest_type, latest_time = max(matching_events, key=lambda x: x[1])
            return latest_time

        return None

    def get_today_logon_time(self) -> Optional[datetime]:
        """
        오늘 첫 로그인 시간 조회 (보조 수단)
        Returns:
            datetime: 첫 로그인 시간, 없으면 None
        """
        try:
            today = datetime.now().date()
            hand = win32evtlog.OpenEventLog(self.server, "Security")
            flags = win32evtlog.EVENTLOG_BACKWARDS_READ | win32evtlog.EVENTLOG_SEQUENTIAL_READ

            events = []
            total = 0

            while total < 100:  # 최근 100개만 확인
                event_list = win32evtlog.ReadEventLog(hand, flags, 0)
                if not event_list:
                    break

                for event in event_list:
                    if event.EventID == self.EVENT_USER_LOGON:
                        event_time = datetime.fromtimestamp(int(event.TimeGenerated))
                        if event_time.date() == today:
                            events.append(event_time)

                total += len(event_list)

            win32evtlog.CloseEventLog(hand)

            if events:
                return min(events)  # 가장 빠른 로그인 시간

        except Exception as e:
            print(f"로그인 이벤트 조회 실패: {e}")

        return None

    def _get_events_by_id(self, event_id: int, limit: int = 10) -> List[Dict]:
        """
        특정 이벤트 ID로 이벤트 조회
        Args:
            event_id: 이벤트 ID
            limit: 조회할 최대 개수
        Returns:
            List[Dict]: 이벤트 목록
        """
        try:
            hand = win32evtlog.OpenEventLog(self.server, self.logtype)
            flags = win32evtlog.EVENTLOG_BACKWARDS_READ | win32evtlog.EVENTLOG_SEQUENTIAL_READ

            events = []
            total = 0

            while len(events) < limit and total < 10000:
                event_list = win32evtlog.ReadEventLog(hand, flags, 0)
                if not event_list:
                    break

                for event in event_list:
                    # 하위 16비트가 실제 이벤트 ID
                    actual_event_id = event.EventID & 0xFFFF

                    if actual_event_id == event_id:
                        # pywintypes.datetime을 Python datetime으로 변환
                        time_generated = event.TimeGenerated
                        if hasattr(time_generated, 'timestamp'):
                            # pywintypes.datetime인 경우
                            event_time = datetime.fromtimestamp(time_generated.timestamp())
                        else:
                            # 이미 타임스탬프인 경우
                            event_time = datetime.fromtimestamp(int(time_generated))

                        events.append({
                            'event_id': actual_event_id,
                            'time': event_time,
                            'source': event.SourceName
                        })

                        if len(events) >= limit:
                            break

                total += len(event_list)

            win32evtlog.CloseEventLog(hand)
            return events

        except Exception as e:
            print(f"이벤트 조회 실패: {e}")
            return []

    def get_work_start_time(self) -> Optional[datetime]:
        """
        출근 시간 자동 감지 (부팅 또는 로그인 중 빠른 시간)
        우선순위:
        1. PowerShell을 통한 부팅 시간 (가장 확실)
        2. 이벤트 뷰어를 통한 부팅 시간
        3. 이벤트 뷰어를 통한 로그인 시간
        Returns:
            datetime: 출근 시간, 없으면 None
        """
        # 1순위: PowerShell로 부팅 시간 조회 (가장 확실한 방법)
        powershell_boot_time = self.get_boot_time_powershell()
        if powershell_boot_time:
            return powershell_boot_time

        # 2순위: 이벤트 뷰어로 부팅 시간 조회
        boot_time = self.get_today_boot_time()
        if boot_time:
            return boot_time

        # 3순위: 로그인 시간 조회
        logon_time = self.get_today_logon_time()
        if logon_time:
            return logon_time

        return None

    def test_event_log_access(self) -> bool:
        """
        이벤트 로그 접근 가능 여부 테스트
        Returns:
            bool: 접근 가능하면 True
        """
        try:
            hand = win32evtlog.OpenEventLog(self.server, self.logtype)
            win32evtlog.CloseEventLog(hand)
            return True
        except Exception as e:
            print(f"이벤트 로그 접근 실패: {e}")
            print("관리자 권한으로 실행해야 할 수 있습니다.")
            return False


# 테스트 코드
if __name__ == "__main__":
    import sys
    # UTF-8 출력 설정
    if sys.platform == 'win32':
        import codecs
        sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer, 'strict')

    monitor = EventMonitor()

    print("=== Windows Event Monitor Test ===\n")

    # PowerShell 부팅 시간 (1순위)
    print("1. PowerShell Boot Time Check:")
    powershell_boot = monitor.get_boot_time_powershell()
    if powershell_boot:
        print(f"   [OK] Boot time: {powershell_boot.strftime('%Y-%m-%d %H:%M:%S')}")
    else:
        print("   [X] PowerShell query failed")

    print("\n2. Event Log Access Check:")
    if monitor.test_event_log_access():
        print("   [OK] Event log access successful")
    else:
        print("   [X] Event log access failed (try running as administrator)")

    print("\n3. Event Viewer Boot Time Check:")
    boot_time = monitor.get_today_boot_time()
    if boot_time:
        print(f"   [OK] Boot time: {boot_time.strftime('%Y-%m-%d %H:%M:%S')}")
    else:
        print("   [X] No boot record found today")

    print("\n4. Event Viewer Logon Time Check:")
    logon_time = monitor.get_today_logon_time()
    if logon_time:
        print(f"   [OK] Logon time: {logon_time.strftime('%Y-%m-%d %H:%M:%S')}")
    else:
        print("   [X] No logon record found today")

    print("\n" + "="*50)
    # 출근 시간 (자동 감지 - 최종 결과)
    work_start = monitor.get_work_start_time()
    if work_start:
        print(f"FINAL: Detected work start time")
        print(f"       {work_start.strftime('%Y-%m-%d %H:%M:%S')}")
    else:
        print("FINAL: Work start time detection FAILED")
    print("="*50)
