from PySide6.QtWidgets import QWidget, QVBoxLayout, QTabWidget, QLabel, QPushButton, QHBoxLayout, QFileDialog
from PySide6.QtCore import Signal
from app.ui.views.graph_view import GraphView
# 추후 구현할 뷰들: from app.ui.views.dashboard_view import DashboardView
from app.data.log_parser import LogLoader
from app.core.sync_controller import sync_manager
from app.ui.views.dashboard_view import DashboardView
from app.ui.views.ai_view import AIDiagnosisView
import polars as pl
from dataclasses import fields
from app.data.fast_parser import FastLogParser
from app.data.log_parser import DOSItoMMIClass

class AnalysisPanel(QWidget):
    # 데이터 로드 완료 시그널
    data_loaded = Signal()
    
    def __init__(self, panel_id, parent=None):
        super().__init__(parent)
        self.panel_id = panel_id # "left" or "right"
        self.loader = None  # 지연 초기화 - 실제 사용 시점에 생성
        self.raw_data = []
        self.data_date = None  # 데이터 날짜 (YYYY.MM.DD 형식)
        self.file_path = None
        self.current_index = None
        self.current_row = None

        self.setup_ui()
        
        # 동기화 신호 받기
        sync_manager.time_changed.connect(self.on_sync_received)

    def setup_ui(self):
        layout = QVBoxLayout(self)
        
        # [상단] 개별 파일 로드 버튼 (A/B 비교를 위해)
        top_bar = QHBoxLayout()
        self.lbl_file = QLabel("No File Loaded")
        self.btn_load = QPushButton(f"Load File ({self.panel_id.upper()})")
        self.btn_load.clicked.connect(self.open_file)
        
        top_bar.addWidget(self.lbl_file)
        top_bar.addWidget(self.btn_load)
        layout.addLayout(top_bar)

        # [중앙] 탭 위젯
        self.tabs = QTabWidget()
        
        # 탭 1: 그래프 뷰
        self.graph_view = GraphView(self.panel_id)
        self.tabs.addTab(self.graph_view, "📊 Graph")
        
        # 탭 2: 기존 뷰어 (껍데기)
        self.dashboard_view = DashboardView(self.panel_id)
        self.tabs.addTab(self.dashboard_view, "📟 Legacy Viewer")

        # 탭 3: 신호 목록 (껍데기)
        self.tabs.addTab(QWidget(), "📜 Signal List")
        
        # 탭 4: AI 분석 (GraphView 컨텍스트 포함)
        self.ai_view = AIDiagnosisView(self.panel_id, graph_view=self.graph_view)
        self.tabs.addTab(self.ai_view, "🤖 AI Diagnosis")

        # 그래프 선택 구간 → AI 요청 연결
        self.graph_view.range_ai_requested.connect(self._on_range_ai_requested)
        
        layout.addWidget(self.tabs)

    def open_file(self):
        file_name, _ = QFileDialog.getOpenFileName(self, "Open Log", "", "Log Files (*.dat)")
        if file_name:
            self.load_data(file_name)

    def load_data(self, file_path):
        self.file_path = file_path
        self.lbl_file.setText(file_path.split("/")[-1])
        # 1. FastLogParser 시도 (Polars)
        parquet_path = file_path.replace(".dat", ".parquet")
        try:
            # 파싱 및 Parquet 저장 (이미 있으면 스킵 가능하지만, 로직상 호출)
            # FastLogParser 내부에서 파일 존재 체크 등을 수행함
            output_path = FastLogParser.parse_to_parquet(file_path)
            
            if output_path:
                # Polars로 읽기
                df = pl.read_parquet(output_path)
                
                # DataFrame -> List[DOSItoMMIClass] 변환
                # 1) 필드 목록 추출
                valid_fields = {f.name for f in fields(DOSItoMMIClass)}
                
                # 2) 딕셔너리 변환 (Polars -> Python List of Dicts)
                # rows = df.to_dicts() # 이 방식은 빠름
                
                # 3) 객체 생성 (불필요한 컬럼 제거)
                # 벡터화된 방식은 아니지만, Python 루프보다는 빠름 (파싱 비용 제거됨)
                # 50만건 기준 약 1~2초 소요 예상
                self.raw_data = [
                    DOSItoMMIClass(**{k: v for k, v in row.items() if k in valid_fields})
                    for row in df.to_dicts()
                ]
                print(f"[AnalysisPanel] Loaded {len(self.raw_data)} records via FastLogParser")
                
            else:
                raise Exception("FastLogParser returned None")

        except Exception as e:
            print(f"[AnalysisPanel] FastLogParser failed, falling back to legacy loader: {e}")
            # LogLoader 지연 초기화
            if self.loader is None:
                self.loader = LogLoader()
            self.raw_data = self.loader.load(file_path)
            
            # 레거시 로더 사용 시 역 정보 매핑 (기존 로직 유지)
            from app.core.reference_data import ref_data
            if ref_data._is_loaded and self.raw_data:
                for item in self.raw_data:
                    if item.pstn > 0:
                        st_name = ref_data.get_station_name(item.pstn)
                        if st_name:
                            item.station_name = st_name

        
        # 데이터 날짜 추출 (첫 번째 레코드의 time 필드에서)
        if self.raw_data and len(self.raw_data) > 0:
            time_str = getattr(self.raw_data[0], 'time', '')
            # time 형식: "20YY.MM.DD HH:mm:ss" -> 날짜 부분만 추출
            if time_str and len(time_str) >= 10:
                self.data_date = time_str[:10]  # "20YY.MM.DD"
            else:
                self.data_date = None
        else:
            self.data_date = None
        
        # 하위 뷰들에게 데이터 전달
        self.graph_view.set_data(self.raw_data)
        self.dashboard_view.set_data(self.raw_data)  # Legacy Viewer에도 전달
        self.ai_view.set_file_context(file_path)
        self.ai_view.set_data(self.raw_data)

        # 기본 커서 포인트(첫 레코드) 설정
        if self.raw_data:
            self.current_index = 0
            self.current_row = self.raw_data[0]
        
        # 첫 번째 패널이 로드되면 전체 레코드 수 설정 (임시)
        if self.panel_id == "left":
            sync_manager._total_records = len(self.raw_data)
        
        # 데이터 로드 완료 시그널 발생
        self.data_loaded.emit()

    def on_sync_received(self, index, data, source_id):
        """
        SyncController로부터 "시간 바꿔!" 명령이 오면 실행
        단, 내가 보낸 신호가 아닐 때만 반응해야 함 (Loop 방지)
        """
        # 내 패널에서 움직인 커서도 기록해둬야 AI/플로팅 채팅에서 현재 시점을 알 수 있음
        if source_id == self.panel_id:
            self.current_index = index
            self.current_row = data
            return

        if source_id != self.panel_id:
            # 타임라인/커서 이동
            self.graph_view.set_index(index)
            # 추후 다른 뷰들도 이동 (예: self.dashboard.set_values(data))

    def _on_range_ai_requested(self, start_ms: int, end_ms: int, start_idx: int, end_idx: int):
        """그래프에서 선택한 구간을 AI 분석으로 전달"""
        if hasattr(self, "ai_view") and self.ai_view:
            self.ai_view.run_range_analysis(start_idx, end_idx, start_ms, end_ms)

    def set_slave_mode(self, is_slave):
        # Sync 모드일 때 로드 버튼 숨기기
        self.btn_load.setVisible(not is_slave)
        self.lbl_file.setText("Linked to Left Panel" if is_slave else "No File")

    def receive_shared_data(self, data_list):
        # 다른 패널에서 데이터 받아오기
        self.raw_data = data_list
        self.graph_view.set_data(data_list)
        self.dashboard_view.set_data(data_list)  # Legacy Viewer에도 전달
        self.ai_view.set_data(data_list)

        if self.raw_data:
            self.current_index = 0
            self.current_row = self.raw_data[0]

    def get_ai_context(self):
        """플로팅 AI/외부 호출용: 현재 패널의 AI 컨텍스트 묶음"""
        ctx = {
            "panel_id": self.panel_id,
            "file_path": self.file_path,
            "data_date": self.data_date,
            "current_index": self.current_index,
        }
        try:
            if hasattr(self, "graph_view") and self.graph_view and hasattr(self.graph_view, "get_ai_context"):
                ctx["graph"] = self.graph_view.get_ai_context(self.current_index)
        except Exception:
            ctx["graph"] = {}

        try:
            if self.current_row is not None:
                d = self.current_row
                ctx["record"] = {
                    "time": getattr(d, "time", ""),
                    "trainno": getattr(d, "trainno", ""),
                    "trainspeed": getattr(d, "trainspeed", None),
                    "dtg": getattr(d, "dtg", None),
                    "atc_status": getattr(d, "atc_status", ""),
                    "marker": getattr(d, "marker", ""),
                    "pstn": getattr(d, "pstn", None),
                }
        except Exception:
            pass
        return ctx

    def compute_pg_at_station(self, marker_name: str = "PG3-2"):
        """
        '각 역마다 PG3-2 위치에서의 속도/TASC' 같은 질의를 위해
        역 구간별(역 이벤트 ~ 다음 역 이벤트)로 PG 마커를 매칭해 지표를 추출한다.
        """
        if not self.raw_data or not hasattr(self, "graph_view") or not self.graph_view:
            return []

        gv = self.graph_view
        if not hasattr(gv, "chart_view") or not getattr(gv, "start_timestamp", 0):
            return []

        # 역 이벤트(시간, 역명)
        try:
            stations = list(gv.chart_view.event_markers.get("STATION", []))
        except Exception:
            stations = []
        stations = [(int(ts), str(name)) for ts, name in stations if ts is not None]
        stations.sort(key=lambda x: x[0])
        if not stations:
            return []

        # PG Rx 마커(시간, 타입)
        try:
            pgs = list(gv.chart_view.event_markers.get("PG", []))
        except Exception:
            pgs = []
        pgs = [(int(ts), str(name)) for ts, name in pgs if ts is not None]
        pgs = [p for p in pgs if p[1] == marker_name]
        pgs.sort(key=lambda x: x[0])

        results = []
        for i, (st_ts, st_name) in enumerate(stations):
            seg_start = st_ts
            seg_end = stations[i + 1][0] if i + 1 < len(stations) else 10**18

            # 구간 내 첫 PG3-2 (없으면 None)
            pg_ts = None
            for t, n in pgs:
                if seg_start <= t < seg_end:
                    pg_ts = t
                    break

            row = {
                "station": st_name,
                "station_ts": st_ts,
                "pg_marker": marker_name,
                "pg_ts": pg_ts,
            }

            if pg_ts is not None:
                idx = int(round((pg_ts - gv.start_timestamp) / 1000.0))
                idx = max(0, min(len(self.raw_data) - 1, idx))
                d = self.raw_data[idx]
                # raw 값
                row.update(
                    {
                        "index": idx,
                        "time": getattr(d, "time", ""),
                        "raw_trainspeed": getattr(d, "trainspeed", None),
                        "raw_tasc_value": getattr(d, "tasc_value", None),
                        "raw_dtg": getattr(d, "dtg", None),
                        "raw_atc_status": getattr(d, "atc_status", ""),
                    }
                )

                # filtered(그래프 표시) 값도 함께
                try:
                    sp = gv.signals_map.get("speed", {}).get("data", [])
                    ta = gv.signals_map.get("tasc", {}).get("data", [])
                    dg = gv.signals_map.get("dtg", {}).get("data", [])
                    if 0 <= idx < len(sp):
                        row["filtered_speed"] = sp[idx]
                    if 0 <= idx < len(ta):
                        row["filtered_tasc"] = ta[idx]
                    if 0 <= idx < len(dg):
                        row["filtered_dtg"] = dg[idx]
                except Exception:
                    pass

                # all-zero 프레임 여부
                try:
                    if hasattr(gv, "_all_zero_mask") and gv._all_zero_mask and 0 <= idx < len(gv._all_zero_mask):
                        row["all_zero_frame"] = bool(gv._all_zero_mask[idx])
                except Exception:
                    pass

                try:
                    # 우선 filtered로 차이 계산, 없으면 raw로 폴백
                    s = row.get("filtered_speed", row.get("raw_trainspeed"))
                    t = row.get("filtered_tasc", row.get("raw_tasc_value"))
                    if s is not None and t is not None:
                        row["speed_minus_tasc"] = float(s) - float(t)
                except Exception:
                    pass

            results.append(row)

        return results

    def get_data_date(self):
        """현재 로드된 데이터의 날짜 반환"""
        return self.data_date
