"""
PIPE 전용 리비전 관리 서비스

Cutting Plan 작성 전/후에 따른 차별화된 리비전 처리 로직
"""

import logging
from typing import Dict, List, Optional, Tuple, Any
from datetime import datetime
from sqlalchemy.orm import Session
from sqlalchemy import text, and_, or_

from ..database import get_db
from ..models import (
    File, Material, PipeCuttingPlan, PipeRevisionComparison, 
    PipeRevisionChange, PipeLengthCalculation
)
from ..utils.pipe_utils import (
    PipeConstants, PipeDataExtractor, PipeCalculator, 
    PipeComparator, PipeValidator, PipeLogger
)

logger = logging.getLogger(__name__)


class PipeRevisionService:
    """PIPE 전용 리비전 관리 서비스"""
    
    def __init__(self, db: Session):
        self.db = db
    
    def check_revision_status(self, job_no: str, new_file_id: int) -> Dict[str, Any]:
        """
        리비전 상태 확인 및 처리 방식 결정
        
        Returns:
            - revision_type: 'no_revision', 'pre_cutting_plan', 'post_cutting_plan'
            - requires_action: 처리가 필요한지 여부
            - message: 사용자에게 표시할 메시지
        """
        try:
            # 기존 파일 확인
            previous_file = self._get_previous_file(job_no, new_file_id)
            if not previous_file:
                return {
                    "revision_type": "no_revision",
                    "requires_action": False,
                    "message": "첫 번째 BOM 파일입니다. 새로운 Cutting Plan을 작성해주세요."
                }
            
            # Cutting Plan 존재 여부 확인
            has_cutting_plan = self._has_existing_cutting_plan(job_no)
            
            if not has_cutting_plan:
                # Cutting Plan 작성 전 리비전
                return {
                    "revision_type": "pre_cutting_plan",
                    "requires_action": True,
                    "previous_file_id": previous_file.id,
                    "message": "Cutting Plan 작성 전 리비전이 감지되었습니다. 새로운 BOM으로 Cutting Plan을 작성해주세요."
                }
            else:
                # Cutting Plan 작성 후 리비전
                return {
                    "revision_type": "post_cutting_plan",
                    "requires_action": True,
                    "previous_file_id": previous_file.id,
                    "message": "기존 Cutting Plan이 있는 상태에서 리비전이 감지되었습니다. 변경사항을 비교 검토해주세요."
                }
                
        except Exception as e:
            logger.error(f"Failed to check pipe revision status: {e}")
            return {
                "revision_type": "error",
                "requires_action": False,
                "message": f"리비전 상태 확인 중 오류가 발생했습니다: {str(e)}"
            }
    
    def handle_pre_cutting_plan_revision(self, job_no: str, new_file_id: int) -> Dict[str, Any]:
        """
        Cutting Plan 작성 전 리비전 처리
        - 기존 PIPE 관련 데이터 전체 삭제
        - 새 BOM 파일로 초기화
        """
        try:
            logger.info(f"Processing pre-cutting-plan revision for job {job_no}")
            
            # 1. 기존 PIPE 관련 데이터 삭제
            deleted_count = self._delete_existing_pipe_data(job_no)
            
            # 2. 새 BOM에서 PIPE 데이터 추출
            pipe_materials = self._extract_pipe_materials_from_bom(new_file_id)
            
            # 3. 처리 결과 반환
            return {
                "status": "success",
                "revision_type": "pre_cutting_plan",
                "deleted_items": deleted_count,
                "new_pipe_materials": len(pipe_materials),
                "message": f"기존 PIPE 데이터 {deleted_count}건이 삭제되었습니다. 새로운 Cutting Plan을 작성해주세요.",
                "next_action": "create_new_cutting_plan",
                "pipe_materials": pipe_materials
            }
            
        except Exception as e:
            logger.error(f"Failed to handle pre-cutting-plan revision: {e}")
            return {
                "status": "error",
                "message": f"Cutting Plan 작성 전 리비전 처리 실패: {str(e)}"
            }
    
    def handle_post_cutting_plan_revision(self, job_no: str, new_file_id: int) -> Dict[str, Any]:
        """
        Cutting Plan 작성 후 리비전 처리
        - 기존 Cutting Plan과 신규 BOM 비교
        - 변경사항 상세 분석
        """
        try:
            logger.info(f"Processing post-cutting-plan revision for job {job_no}")
            
            # 1. 기존 Cutting Plan 조회
            existing_plan = self._get_existing_cutting_plan_data(job_no)
            if not existing_plan:
                return {
                    "status": "error",
                    "message": "기존 Cutting Plan을 찾을 수 없습니다."
                }
            
            # 2. 새 BOM에서 PIPE 데이터 추출
            new_pipe_data = self._extract_pipe_materials_from_bom(new_file_id)
            
            # 3. 도면별 비교 수행
            comparison_result = self._compare_pipe_data_by_drawing(existing_plan, new_pipe_data)
            
            # 4. 비교 결과 저장
            comparison_id = self._save_comparison_result(job_no, new_file_id, comparison_result)
            
            # 5. 변경사항 요약
            summary = self._generate_comparison_summary(comparison_result)
            
            return {
                "status": "success",
                "revision_type": "post_cutting_plan",
                "comparison_id": comparison_id,
                "summary": summary,
                "changed_drawings": [d for d in comparison_result if d["has_changes"]],
                "unchanged_drawings": [d for d in comparison_result if not d["has_changes"]],
                "message": f"리비전 비교가 완료되었습니다. {summary['changed_drawings_count']}개 도면에서 변경사항이 발견되었습니다.",
                "next_action": "review_changes"
            }
            
        except Exception as e:
            logger.error(f"Failed to handle post-cutting-plan revision: {e}")
            return {
                "status": "error",
                "message": f"Cutting Plan 작성 후 리비전 처리 실패: {str(e)}"
            }
    
    def _get_previous_file(self, job_no: str, current_file_id: int) -> Optional[File]:
        """이전 파일 조회"""
        return self.db.query(File).filter(
            and_(
                File.job_no == job_no,
                File.id < current_file_id,
                File.is_active == True
            )
        ).order_by(File.id.desc()).first()
    
    def _has_existing_cutting_plan(self, job_no: str) -> bool:
        """기존 Cutting Plan 존재 여부 확인"""
        count = self.db.query(PipeCuttingPlan).filter(
            PipeCuttingPlan.job_no == job_no
        ).count()
        return count > 0
    
    def _delete_existing_pipe_data(self, job_no: str) -> int:
        """기존 PIPE 관련 데이터 삭제"""
        try:
            # Cutting Plan 데이터 삭제
            cutting_plan_count = self.db.query(PipeCuttingPlan).filter(
                PipeCuttingPlan.job_no == job_no
            ).count()
            
            self.db.query(PipeCuttingPlan).filter(
                PipeCuttingPlan.job_no == job_no
            ).delete()
            
            # Length Calculation 데이터 삭제
            self.db.query(PipeLengthCalculation).filter(
                PipeLengthCalculation.file_id.in_(
                    self.db.query(File.id).filter(File.job_no == job_no)
                )
            ).delete()
            
            self.db.commit()
            logger.info(f"Deleted {cutting_plan_count} cutting plan records for job {job_no}")
            
            return cutting_plan_count
            
        except Exception as e:
            self.db.rollback()
            logger.error(f"Failed to delete existing pipe data: {e}")
            raise
    
    def _extract_pipe_materials_from_bom(self, file_id: int) -> List[Dict[str, Any]]:
        """BOM 파일에서 PIPE 자재 추출 (리팩토링된 유틸리티 사용)"""
        return PipeDataExtractor.extract_pipe_materials_from_file(self.db, file_id)
    
    def _get_existing_cutting_plan_data(self, job_no: str) -> List[Dict[str, Any]]:
        """기존 Cutting Plan 데이터 조회"""
        try:
            cutting_plans = self.db.query(PipeCuttingPlan).filter(
                PipeCuttingPlan.job_no == job_no
            ).all()
            
            plan_data = []
            for plan in cutting_plans:
                plan_data.append({
                    "id": plan.id,
                    "area": plan.area or "",
                    "drawing_name": plan.drawing_name,
                    "line_no": plan.line_no,
                    "material_grade": plan.material_grade or "",
                    "schedule_spec": plan.schedule_spec or "",
                    "nominal_size": plan.nominal_size or "",
                    "length_mm": float(plan.length_mm or 0),
                    "end_preparation": plan.end_preparation or "무개선"
                })
            
            logger.info(f"Retrieved {len(plan_data)} cutting plan records for job {job_no}")
            return plan_data
            
        except Exception as e:
            logger.error(f"Failed to get existing cutting plan data: {e}")
            raise
    
    def _compare_pipe_data_by_drawing(self, existing_plan: List[Dict], new_pipe_data: List[Dict]) -> List[Dict[str, Any]]:
        """도면별 PIPE 데이터 비교"""
        try:
            # 도면별로 데이터 그룹화
            existing_by_drawing = self._group_by_drawing(existing_plan)
            new_by_drawing = self._group_by_drawing(new_pipe_data)
            
            # 모든 도면 목록
            all_drawings = set(existing_by_drawing.keys()) | set(new_by_drawing.keys())
            
            comparison_results = []
            
            for drawing_name in sorted(all_drawings):
                existing_segments = existing_by_drawing.get(drawing_name, [])
                new_segments = new_by_drawing.get(drawing_name, [])
                
                # 도면별 비교 수행
                drawing_comparison = self._compare_drawing_segments(
                    drawing_name, existing_segments, new_segments
                )
                
                comparison_results.append(drawing_comparison)
            
            return comparison_results
            
        except Exception as e:
            logger.error(f"Failed to compare pipe data by drawing: {e}")
            raise
    
    def _group_by_drawing(self, data: List[Dict]) -> Dict[str, List[Dict]]:
        """데이터를 도면별로 그룹화"""
        grouped = {}
        for item in data:
            drawing = item.get("drawing_name", "UNKNOWN")
            if drawing not in grouped:
                grouped[drawing] = []
            grouped[drawing].append(item)
        return grouped
    
    def _compare_drawing_segments(self, drawing_name: str, existing: List[Dict], new: List[Dict]) -> Dict[str, Any]:
        """단일 도면의 세그먼트 비교"""
        try:
            # 세그먼트 매칭 (재질, 길이, 끝단가공 기준)
            matched_pairs, added_segments, removed_segments = self._match_segments(existing, new)
            
            # 변경사항 분석
            unchanged_segments = []
            modified_segments = []
            
            for existing_seg, new_seg in matched_pairs:
                if self._segments_are_identical(existing_seg, new_seg):
                    unchanged_segments.append({
                        "change_type": "unchanged",
                        "segment_data": new_seg,
                        "existing_data": existing_seg
                    })
                else:
                    changes = self._get_segment_changes(existing_seg, new_seg)
                    modified_segments.append({
                        "change_type": "modified",
                        "segment_data": new_seg,
                        "existing_data": existing_seg,
                        "changes": changes
                    })
            
            # 추가된 세그먼트
            added_segment_data = [
                {
                    "change_type": "added",
                    "segment_data": seg,
                    "existing_data": None
                }
                for seg in added_segments
            ]
            
            # 삭제된 세그먼트
            removed_segment_data = [
                {
                    "change_type": "removed",
                    "segment_data": None,
                    "existing_data": seg
                }
                for seg in removed_segments
            ]
            
            # 전체 세그먼트 목록
            all_segments = unchanged_segments + modified_segments + added_segment_data + removed_segment_data
            
            # 변경사항 여부 판단
            has_changes = len(modified_segments) > 0 or len(added_segments) > 0 or len(removed_segments) > 0
            
            return {
                "drawing_name": drawing_name,
                "has_changes": has_changes,
                "segments": all_segments,
                "summary": {
                    "total_segments": len(all_segments),
                    "unchanged_count": len(unchanged_segments),
                    "modified_count": len(modified_segments),
                    "added_count": len(added_segments),
                    "removed_count": len(removed_segments)
                }
            }
            
        except Exception as e:
            logger.error(f"Failed to compare segments for drawing {drawing_name}: {e}")
            raise
    
    def _match_segments(self, existing: List[Dict], new: List[Dict]) -> Tuple[List[Tuple], List[Dict], List[Dict]]:
        """세그먼트 매칭 (재질, 길이 기준)"""
        matched_pairs = []
        remaining_new = new.copy()
        remaining_existing = existing.copy()
        
        # 정확히 일치하는 세그먼트 찾기
        for existing_seg in existing.copy():
            for new_seg in remaining_new.copy():
                if self._segments_match_for_pairing(existing_seg, new_seg):
                    matched_pairs.append((existing_seg, new_seg))
                    remaining_existing.remove(existing_seg)
                    remaining_new.remove(new_seg)
                    break
        
        # 남은 것들은 추가/삭제로 분류
        added_segments = remaining_new
        removed_segments = remaining_existing
        
        return matched_pairs, added_segments, removed_segments
    
    def _segments_match_for_pairing(self, seg1: Dict, seg2: Dict) -> bool:
        """세그먼트 매칭 기준 (재질과 길이가 유사한지 확인)"""
        # 재질 비교
        material1 = seg1.get("material_grade", "").strip()
        material2 = seg2.get("material_grade", "").strip()
        
        # 길이 비교 (허용 오차 1mm)
        length1 = seg1.get("length_mm", seg1.get("length", 0))
        length2 = seg2.get("length_mm", seg2.get("length", 0))
        
        material_match = material1.lower() == material2.lower()
        length_match = abs(float(length1) - float(length2)) <= 1.0
        
        return material_match and length_match
    
    def _segments_are_identical(self, seg1: Dict, seg2: Dict) -> bool:
        """세그먼트 완전 동일성 검사"""
        # 주요 속성들 비교
        material_match = seg1.get("material_grade", "").strip().lower() == seg2.get("material_grade", "").strip().lower()
        
        length1 = seg1.get("length_mm", seg1.get("length", 0))
        length2 = seg2.get("length_mm", seg2.get("length", 0))
        length_match = abs(float(length1) - float(length2)) <= 0.1
        
        end_prep1 = seg1.get("end_preparation", "무개선")
        end_prep2 = seg2.get("end_preparation", "무개선")
        end_prep_match = end_prep1 == end_prep2
        
        return material_match and length_match and end_prep_match
    
    def _get_segment_changes(self, existing: Dict, new: Dict) -> List[Dict[str, Any]]:
        """세그먼트 변경사항 상세 분석"""
        changes = []
        
        # 재질 변경
        old_material = existing.get("material_grade", "").strip()
        new_material = new.get("material_grade", "").strip()
        if old_material.lower() != new_material.lower():
            changes.append({
                "field": "material_grade",
                "old_value": old_material,
                "new_value": new_material
            })
        
        # 길이 변경
        old_length = existing.get("length_mm", existing.get("length", 0))
        new_length = new.get("length_mm", new.get("length", 0))
        if abs(float(old_length) - float(new_length)) > 0.1:
            changes.append({
                "field": "length",
                "old_value": f"{old_length}mm",
                "new_value": f"{new_length}mm"
            })
        
        # 끝단가공 변경
        old_end_prep = existing.get("end_preparation", "무개선")
        new_end_prep = new.get("end_preparation", "무개선")
        if old_end_prep != new_end_prep:
            changes.append({
                "field": "end_preparation",
                "old_value": old_end_prep,
                "new_value": new_end_prep
            })
        
        return changes
    
    def _save_comparison_result(self, job_no: str, new_file_id: int, comparison_result: List[Dict]) -> int:
        """비교 결과를 데이터베이스에 저장"""
        try:
            # 이전 파일 ID 조회
            previous_file = self._get_previous_file(job_no, new_file_id)
            previous_file_id = previous_file.id if previous_file else None
            
            # 통계 계산
            total_drawings = len(comparison_result)
            changed_drawings = len([d for d in comparison_result if d["has_changes"]])
            unchanged_drawings = total_drawings - changed_drawings
            
            total_segments = sum(d["summary"]["total_segments"] for d in comparison_result)
            added_segments = sum(d["summary"]["added_count"] for d in comparison_result)
            removed_segments = sum(d["summary"]["removed_count"] for d in comparison_result)
            modified_segments = sum(d["summary"]["modified_count"] for d in comparison_result)
            unchanged_segments = sum(d["summary"]["unchanged_count"] for d in comparison_result)
            
            # 비교 결과 저장
            comparison = PipeRevisionComparison(
                job_no=job_no,
                current_file_id=new_file_id,
                previous_cutting_plan_id=None,  # 추후 구현
                total_drawings=total_drawings,
                changed_drawings=changed_drawings,
                unchanged_drawings=unchanged_drawings,
                total_segments=total_segments,
                added_segments=added_segments,
                removed_segments=removed_segments,
                modified_segments=modified_segments,
                unchanged_segments=unchanged_segments,
                created_by="system"
            )
            
            self.db.add(comparison)
            self.db.flush()  # ID 생성을 위해
            
            # 상세 변경사항 저장
            for drawing_data in comparison_result:
                if drawing_data["has_changes"]:
                    for segment in drawing_data["segments"]:
                        if segment["change_type"] != "unchanged":
                            change = PipeRevisionChange(
                                comparison_id=comparison.id,
                                drawing_name=drawing_data["drawing_name"],
                                change_type=segment["change_type"]
                            )
                            
                            # 기존 데이터
                            if segment["existing_data"]:
                                existing = segment["existing_data"]
                                change.old_line_no = existing.get("line_no")
                                change.old_material_grade = existing.get("material_grade")
                                change.old_schedule_spec = existing.get("schedule_spec")
                                change.old_nominal_size = existing.get("nominal_size")
                                change.old_length_mm = existing.get("length_mm", existing.get("length"))
                                change.old_end_preparation = existing.get("end_preparation")
                            
                            # 새 데이터
                            if segment["segment_data"]:
                                new_data = segment["segment_data"]
                                change.new_line_no = new_data.get("line_no")
                                change.new_material_grade = new_data.get("material_grade")
                                change.new_schedule_spec = new_data.get("schedule_spec")
                                change.new_nominal_size = new_data.get("nominal_size")
                                change.new_length_mm = new_data.get("length_mm", new_data.get("length"))
                                change.new_end_preparation = new_data.get("end_preparation")
                            
                            self.db.add(change)
            
            self.db.commit()
            logger.info(f"Saved comparison result with ID {comparison.id}")
            
            return comparison.id
            
        except Exception as e:
            self.db.rollback()
            logger.error(f"Failed to save comparison result: {e}")
            raise
    
    def _generate_comparison_summary(self, comparison_result: List[Dict]) -> Dict[str, Any]:
        """비교 결과 요약 생성"""
        total_drawings = len(comparison_result)
        changed_drawings = [d for d in comparison_result if d["has_changes"]]
        changed_drawings_count = len(changed_drawings)
        
        total_segments = sum(d["summary"]["total_segments"] for d in comparison_result)
        added_segments = sum(d["summary"]["added_count"] for d in comparison_result)
        removed_segments = sum(d["summary"]["removed_count"] for d in comparison_result)
        modified_segments = sum(d["summary"]["modified_count"] for d in comparison_result)
        unchanged_segments = sum(d["summary"]["unchanged_count"] for d in comparison_result)
        
        return {
            "total_drawings": total_drawings,
            "changed_drawings_count": changed_drawings_count,
            "unchanged_drawings_count": total_drawings - changed_drawings_count,
            "total_segments": total_segments,
            "added_segments": added_segments,
            "removed_segments": removed_segments,
            "modified_segments": modified_segments,
            "unchanged_segments": unchanged_segments,
            "change_percentage": round((changed_drawings_count / total_drawings * 100) if total_drawings > 0 else 0, 1)
        }


def get_pipe_revision_service(db: Session = None) -> PipeRevisionService:
    """PipeRevisionService 인스턴스 생성"""
    if db is None:
        db = next(get_db())
    return PipeRevisionService(db)
