import logging

from sqlalchemy.orm import Session
from sqlalchemy import text
from typing import List, Dict, Optional
import json
from datetime import datetime

from app.services.integrated_classifier import classify_material_integrated, should_exclude_material
from app.services.bolt_classifier import classify_bolt
from app.services.flange_classifier import classify_flange
from app.services.fitting_classifier import classify_fitting
from app.services.gasket_classifier import classify_gasket
from app.services.instrument_classifier import classify_instrument
from app.services.valve_classifier import classify_valve
from app.services.support_classifier import classify_support
from app.services.plate_classifier import classify_plate
from app.services.structural_classifier import classify_structural
from app.services.pipe_classifier import classify_pipe_for_purchase, extract_end_preparation_info
from app.services.material_grade_extractor import extract_full_material_grade

logger = logging.getLogger(__name__)

class MaterialService:
    """자재 처리 및 저장을 담당하는 서비스"""

    @staticmethod
    def process_and_save_materials(
        db: Session,
        file_id: int,
        materials_data: List[Dict],
        revision_comparison: Optional[Dict] = None,
        parent_file_id: Optional[int] = None,
        purchased_materials_map: Optional[Dict] = None
    ) -> int:
        """
        자재 목록을 분류하고 DB에 저장합니다.
        
        Args:
            db: DB 세션
            file_id: 파일 ID
            materials_data: 파싱된 자재 데이터 목록
            revision_comparison: 리비전 비교 결과
            parent_file_id: 이전 리비전 파일 ID
            purchased_materials_map: 구매 확정된 자재 매핑 정보
            
        Returns:
            저장된 자재 수
        """
        materials_inserted = 0
        
        # 변경/신규 자재 키 집합 (리비전 추적용)
        changed_materials_keys = set()
        new_materials_keys = set()
        
        # 리비전 업로드인 경우 변경사항 분석
        if parent_file_id is not None:
            MaterialService._analyze_changes(
                db, parent_file_id, materials_data, 
                changed_materials_keys, new_materials_keys
            )
        
        # 변경 없는 자재 (확정된 자재) 먼저 처리
        if revision_comparison and revision_comparison.get("has_previous_confirmation", False):
            unchanged_materials = revision_comparison.get("unchanged_materials", [])
            for material_data in unchanged_materials:
                MaterialService._save_unchanged_material(db, file_id, material_data)
                materials_inserted += 1
                
        # 분류가 필요한 자재 처리 (신규 또는 변경된 자재)
        # revision_comparison에서 필터링된 목록이 있으면 그것을 사용, 아니면 전체
        materials_to_classify = materials_data
        if revision_comparison and revision_comparison.get("materials_to_classify"):
            materials_to_classify = revision_comparison.get("materials_to_classify")
            
        logger.info(f"자재 분류 및 저장 시작: {len(materials_to_classify)}개")
        
        for material_data in materials_to_classify:
            MaterialService._classify_and_save_single_material(
                db, file_id, material_data, 
                changed_materials_keys, new_materials_keys,
                purchased_materials_map
            )
            materials_inserted += 1
            
        return materials_inserted

    @staticmethod
    def _analyze_changes(db: Session, parent_file_id: int, materials_data: List[Dict], 
                        changed_keys: set, new_keys: set):
        """이전 리비전과 비교하여 변경/신규 자재를 식별합니다."""
        try:
            prev_materials_query = text("""
                SELECT original_description, size_spec, material_grade, main_nom, 
                       drawing_name, line_no, quantity
                FROM materials
                WHERE file_id = :parent_file_id
            """)
            prev_materials = db.execute(prev_materials_query, {"parent_file_id": parent_file_id}).fetchall()
            
            prev_dict = {}
            for pm in prev_materials:
                key = MaterialService._generate_material_key(
                    pm.drawing_name, pm.line_no, pm.original_description, 
                    pm.size_spec, pm.material_grade
                )
                prev_dict[key] = float(pm.quantity) if pm.quantity else 0
            
            for mat in materials_data:
                new_key = MaterialService._generate_material_key(
                    mat.get("dwg_name"), mat.get("line_num"), mat["original_description"],
                    mat.get("size_spec"), mat.get("material_grade")
                )
                
                if new_key in prev_dict:
                    if abs(prev_dict[new_key] - float(mat.get("quantity", 0))) > 0.001:
                        changed_keys.add(new_key)
                else:
                    new_keys.add(new_key)
                    
        except Exception as e:
            logger.error(f"변경사항 분석 실패: {e}")

    @staticmethod
    def _generate_material_key(dwg, line, desc, size, grade):
        """자재 고유 키 생성"""
        parts = []
        if dwg: parts.append(str(dwg))
        elif line: parts.append(str(line))
        
        parts.append(str(desc))
        parts.append(str(size or ''))
        parts.append(str(grade or ''))
        return "|".join(parts)

    @staticmethod
    def _save_unchanged_material(db: Session, file_id: int, material_data: Dict):
        """변경 없는(확정된) 자재 저장"""
        previous_item = material_data.get("previous_item", {})
        
        query = text("""
            INSERT INTO materials (
                file_id, original_description, classified_category, confidence,
                quantity, unit, size_spec, material_grade, specification,
                reused_from_confirmation, created_at
            ) VALUES (
                :file_id, :desc, :category, 1.0,
                :qty, :unit, :size, :grade, :spec,
                TRUE, :created_at
            )
        """)
        
        db.execute(query, {
            "file_id": file_id,
            "desc": material_data["original_description"],
            "category": previous_item.get("category", "UNCLASSIFIED"),
            "qty": material_data["quantity"],
            "unit": material_data.get("unit", "EA"),
            "size": material_data.get("size_spec", ""),
            "grade": previous_item.get("material", ""),
            "spec": previous_item.get("specification", ""),
            "created_at": datetime.now()
        })

    @staticmethod
    def _classify_and_save_single_material(
        db: Session, file_id: int, material_data: Dict,
        changed_keys: set, new_keys: set, purchased_map: Optional[Dict]
    ):
        """단일 자재 분류 및 저장 (상세 정보 포함)"""
        description = material_data["original_description"]
        main_nom = material_data.get("main_nom", "")
        red_nom = material_data.get("red_nom", "")
        length_val = material_data.get("length")
        
        # 1. 통합 분류
        integrated_result = classify_material_integrated(description, main_nom, red_nom, length_val)
        classification_result = integrated_result
        
        # 2. 상세 분류
        if not should_exclude_material(description):
            category = integrated_result.get('category')
            if category == "PIPE":
                classification_result = classify_pipe_for_purchase("", description, main_nom, length_val)
            elif category == "FITTING":
                classification_result = classify_fitting("", description, main_nom, red_nom)
            elif category == "FLANGE":
                classification_result = classify_flange("", description, main_nom, red_nom)
            elif category == "VALVE":
                classification_result = classify_valve("", description, main_nom)
            elif category == "BOLT":
                classification_result = classify_bolt("", description, main_nom)
            elif category == "GASKET":
                classification_result = classify_gasket("", description, main_nom)
            elif category == "INSTRUMENT":
                classification_result = classify_instrument("", description, main_nom)
            elif category == "SUPPORT":
                classification_result = classify_support("", description, main_nom)
            elif category == "PLATE":
                classification_result = classify_plate("", description, main_nom)
            elif category == "STRUCTURAL":
                classification_result = classify_structural("", description, main_nom)
                
            # 신뢰도 조정
            if integrated_result.get('confidence', 0) < 0.5:
                classification_result['overall_confidence'] = min(
                    classification_result.get('overall_confidence', 1.0),
                    integrated_result.get('confidence', 0.0) + 0.2
                )
        else:
             classification_result = {"category": "EXCLUDE", "overall_confidence": 0.95}

        # 3. 구매 확정 정보 상속 확인
        is_purchase_confirmed = False
        purchase_confirmed_at = None
        purchase_confirmed_by = None
        
        if purchased_map:
            key = f"{description.strip().upper()}|{material_data.get('size_spec', '')}"
            if key in purchased_map:
                info = purchased_map[key]
                is_purchase_confirmed = True
                purchase_confirmed_at = info.get("purchase_confirmed_at")
                purchase_confirmed_by = info.get("purchase_confirmed_by")

        # 4. 자재 기본 정보 저장
        full_grade = extract_full_material_grade(description) or material_data.get("material_grade", "")
        
        insert_query = text("""
            INSERT INTO materials (
                file_id, original_description, quantity, unit, size_spec,
                main_nom, red_nom, material_grade, full_material_grade, line_number, row_number,
                classified_category, classification_confidence, is_verified,
                drawing_name, line_no, created_at,
                purchase_confirmed, purchase_confirmed_at, purchase_confirmed_by,
                revision_status
            ) VALUES (
                :file_id, :desc, :qty, :unit, :size,
                :main, :red, :grade, :full_grade, :line_num, :row_num,
                :category, :confidence, :verified,
                :dwg, :line, :created_at,
                :confirmed, :confirmed_at, :confirmed_by,
                :status
            ) RETURNING id
        """)
        
        # 리비전 상태 결정
        mat_key = MaterialService._generate_material_key(
            material_data.get("dwg_name"), material_data.get("line_num"), description,
            material_data.get("size_spec"), material_data.get("material_grade")
        )
        rev_status = 'changed' if mat_key in changed_keys else ('active' if mat_key in new_keys else None)
        
        result = db.execute(insert_query, {
            "file_id": file_id,
            "desc": description,
            "qty": material_data["quantity"],
            "unit": material_data["unit"],
            "size": material_data.get("size_spec", ""),
            "main": main_nom,
            "red": red_nom,
            "grade": material_data.get("material_grade", ""),
            "full_grade": full_grade,
            "line_num": material_data.get("line_number"),
            "row_num": material_data.get("row_number"),
            "category": classification_result.get("category", "UNCLASSIFIED"),
            "confidence": classification_result.get("overall_confidence", 0.0),
            "verified": False,
            "dwg": material_data.get("dwg_name"),
            "line": material_data.get("line_num"),
            "created_at": datetime.now(),
            "confirmed": is_purchase_confirmed,
            "confirmed_at": purchase_confirmed_at,
            "confirmed_by": purchase_confirmed_by,
            "status": rev_status
        })
        
        material_id = result.fetchone()[0]
        
        # 5. 상세 정보 저장 (별도 메서드로 분리)
        MaterialService._save_material_details(
            db, material_id, file_id, classification_result, material_data
        )

    @staticmethod
    def _save_material_details(db: Session, material_id: int, file_id: int, 
                             result: Dict, data: Dict):
        """카테고리별 상세 정보 저장"""
        category = result.get("category")
        
        if category == "PIPE":
            MaterialService._save_pipe_details(db, material_id, file_id, result, data)
        elif category == "FITTING":
            MaterialService._save_fitting_details(db, material_id, file_id, result, data)
        elif category == "FLANGE":
            MaterialService._save_flange_details(db, material_id, file_id, result, data)
        elif category == "BOLT":
            MaterialService._save_bolt_details(db, material_id, file_id, result, data)
        elif category == "VALVE":
            MaterialService._save_valve_details(db, material_id, file_id, result, data)
        elif category == "GASKET":
            MaterialService._save_gasket_details(db, material_id, file_id, result, data)
        elif category == "SUPPORT":
            MaterialService._save_support_details(db, material_id, file_id, result, data)
        elif category == "PLATE":
            MaterialService._save_plate_details(db, material_id, file_id, result, data)
        elif category == "STRUCTURAL":
            MaterialService._save_structural_details(db, material_id, file_id, result, data)

    @staticmethod
    def _save_plate_details(db: Session, mid: int, fid: int, res: Dict, data: Dict):
        """판재 정보 업데이트 (현재는 materials 테이블에 요약 정보 저장)"""
        details = res.get("details", {})
        spec = f"{details.get('thickness')}T x {details.get('dimensions')}"
        db.execute(text("""
            UPDATE materials 
            SET size_spec = :size, material_grade = :mat
            WHERE id = :id
        """), {"size": spec, "mat": details.get("material"), "id": mid})

    @staticmethod
    def _save_structural_details(db: Session, mid: int, fid: int, res: Dict, data: Dict):
        """형강 정보 업데이트 (현재는 materials 테이블에 요약 정보 저장)"""
        details = res.get("details", {})
        spec = f"{details.get('type')} {details.get('dimension')}"
        db.execute(text("""
            UPDATE materials 
            SET size_spec = :size
            WHERE id = :id
        """), {"size": spec, "id": mid})

    # --- 각 카테고리별 상세 저장 메서드들 (기존 로직 이관) ---
    
    @staticmethod
    def inherit_purchase_requests(db: Session, current_file_id: int, parent_file_id: int):
        """이전 리비전의 구매신청 정보를 상속합니다."""
        try:
            logger.info(f"구매신청 정보 상속 처리 시작...")
            
            # 1. 이전 리비전에서 그룹별 구매신청 수량 집계
            prev_purchase_summary = text("""
                SELECT 
                    m.original_description,
                    m.size_spec,
                    m.material_grade,
                    m.drawing_name,
                    COUNT(DISTINCT pri.material_id) as purchased_count,
                    SUM(pri.quantity) as total_purchased_qty,
                    MIN(pri.request_id) as request_id
                FROM materials m
                JOIN purchase_request_items pri ON m.id = pri.material_id
                WHERE m.file_id = :parent_file_id
                GROUP BY m.original_description, m.size_spec, m.material_grade, m.drawing_name
            """)
            
            prev_purchases = db.execute(prev_purchase_summary, {"parent_file_id": parent_file_id}).fetchall()
            
            # 2. 새 리비전에서 같은 그룹의 자재에 수량만큼 구매신청 상속
            for prev_purchase in prev_purchases:
                purchased_count = prev_purchase.purchased_count
                
                # 새 리비전에서 같은 그룹의 자재 조회 (순서대로)
                new_group_materials = text("""
                    SELECT id, quantity
                    FROM materials
                    WHERE file_id = :file_id
                    AND original_description = :description
                    AND COALESCE(size_spec, '') = :size_spec
                    AND COALESCE(material_grade, '') = :material_grade
                    AND COALESCE(drawing_name, '') = :drawing_name
                    ORDER BY id
                    LIMIT :limit
                """)
                
                new_materials = db.execute(new_group_materials, {
                    "file_id": current_file_id,
                    "description": prev_purchase.original_description,
                    "size_spec": prev_purchase.size_spec or '',
                    "material_grade": prev_purchase.material_grade or '',
                    "drawing_name": prev_purchase.drawing_name or '',
                    "limit": purchased_count
                }).fetchall()
                
                # 구매신청 수량만큼만 상속
                for new_mat in new_materials:
                    inherit_query = text("""
                        INSERT INTO purchase_request_items (
                            request_id, material_id, quantity, unit, user_requirement
                        ) VALUES (
                            :request_id, :material_id, :quantity, 'EA', ''
                        )
                        ON CONFLICT DO NOTHING
                    """)
                    db.execute(inherit_query, {
                        "request_id": prev_purchase.request_id,
                        "material_id": new_mat.id,
                        "quantity": new_mat.quantity
                    })
                
                inherited_count = len(new_materials)
                if inherited_count > 0:
                    logger.info(f"  {prev_purchase.original_description[:30]}... (도면: {prev_purchase.drawing_name or 'N/A'}) -> {inherited_count}/{purchased_count}개 상속")
            
            # 커밋은 호출하는 쪽에서 일괄 처리하거나 여기서 처리
            # db.commit() 
            logger.info(f"구매신청 정보 상속 완료")
            
        except Exception as e:
            logger.error(f"구매신청 정보 상속 실패: {str(e)}")
            # 상속 실패는 전체 프로세스를 중단하지 않음

    @staticmethod
    def _save_pipe_details(db, mid, fid, res, data):
        # PIPE 상세 저장 로직
        end_prep_info = extract_end_preparation_info(data["original_description"])
        
        # 1. End Prep 정보 저장
        db.execute(text("""
            INSERT INTO pipe_end_preparations (
                material_id, file_id, end_preparation_type, end_preparation_code,
                machining_required, cutting_note, original_description, confidence
            ) VALUES (
                :mid, :fid, :type, :code, :req, :note, :desc, :conf
            )
        """), {
            "mid": mid, "fid": fid,
            "type": end_prep_info["end_preparation_type"],
            "code": end_prep_info["end_preparation_code"],
            "req": end_prep_info["machining_required"],
            "note": end_prep_info["cutting_note"],
            "desc": end_prep_info["original_description"],
            "conf": end_prep_info["confidence"]
        })
        
        # 2. Pipe Details 저장
        length_info = res.get("length_info", {})
        length_mm = length_info.get("length_mm") or data.get("length", 0.0)
        
        mat_info = res.get("material", {})
        sch_info = res.get("schedule", {})
        
        # 재질 정보 업데이트
        if mat_info.get("grade"):
            db.execute(text("UPDATE materials SET material_grade = :g WHERE id = :id"), 
                      {"g": mat_info.get("grade"), "id": mid})

        db.execute(text("""
            INSERT INTO pipe_details (
                material_id, file_id, outer_diameter, schedule,
                material_spec, manufacturing_method, length_mm
            ) VALUES (:mid, :fid, :od, :sch, :spec, :method, :len)
        """), {
            "mid": mid, "fid": fid,
            "od": data.get("main_nom") or data.get("size_spec"),
            "sch": sch_info.get("schedule", "UNKNOWN") if isinstance(sch_info, dict) else str(sch_info),
            "spec": mat_info.get("grade", "") if isinstance(mat_info, dict) else "",
            "method": res.get("manufacturing", {}).get("method", "UNKNOWN") if isinstance(res.get("manufacturing"), dict) else "UNKNOWN",
            "len": length_mm or 0.0
        })

    @staticmethod
    def _save_fitting_details(db, mid, fid, res, data):
        fit_type = res.get("fitting_type", {})
        mat_info = res.get("material", {})
        
        if mat_info.get("grade"):
            db.execute(text("UPDATE materials SET material_grade = :g WHERE id = :id"), 
                      {"g": mat_info.get("grade"), "id": mid})

        db.execute(text("""
            INSERT INTO fitting_details (
                material_id, file_id, fitting_type, fitting_subtype,
                connection_method, pressure_rating, material_grade,
                main_size, reduced_size
            ) VALUES (:mid, :fid, :type, :subtype, :conn, :rating, :grade, :main, :red)
        """), {
            "mid": mid, "fid": fid,
            "type": fit_type.get("type", "UNKNOWN") if isinstance(fit_type, dict) else str(fit_type),
            "subtype": fit_type.get("subtype", "UNKNOWN") if isinstance(fit_type, dict) else "UNKNOWN",
            "conn": res.get("connection_method", {}).get("method", "UNKNOWN") if isinstance(res.get("connection_method"), dict) else "UNKNOWN",
            "rating": res.get("pressure_rating", {}).get("rating", "UNKNOWN") if isinstance(res.get("pressure_rating"), dict) else "UNKNOWN",
            "grade": mat_info.get("grade", "") if isinstance(mat_info, dict) else "",
            "main": data.get("main_nom") or data.get("size_spec"),
            "red": data.get("red_nom", "")
        })

    @staticmethod
    def _save_flange_details(db, mid, fid, res, data):
        flg_type = res.get("flange_type", {})
        mat_info = res.get("material", {})

        if mat_info.get("grade"):
            db.execute(text("UPDATE materials SET material_grade = :g WHERE id = :id"), 
                      {"g": mat_info.get("grade"), "id": mid})

        db.execute(text("""
            INSERT INTO flange_details (
                material_id, file_id, flange_type, pressure_rating,
                facing_type, material_grade, size_inches
            ) VALUES (:mid, :fid, :type, :rating, :face, :grade, :size)
        """), {
            "mid": mid, "fid": fid,
            "type": flg_type.get("type", "UNKNOWN") if isinstance(flg_type, dict) else str(flg_type),
            "rating": res.get("pressure_rating", {}).get("rating", "UNKNOWN") if isinstance(res.get("pressure_rating"), dict) else "UNKNOWN",
            "face": res.get("face_finish", {}).get("finish", "UNKNOWN") if isinstance(res.get("face_finish"), dict) else "UNKNOWN",
            "grade": mat_info.get("grade", "") if isinstance(mat_info, dict) else "",
            "size": data.get("main_nom") or data.get("size_spec")
        })

    @staticmethod
    def _save_bolt_details(db, mid, fid, res, data):
        fast_type = res.get("fastener_type", {})
        mat_info = res.get("material", {})
        dim_info = res.get("dimensions", {})
        
        if mat_info.get("grade"):
            db.execute(text("UPDATE materials SET material_grade = :g WHERE id = :id"), 
                      {"g": mat_info.get("grade"), "id": mid})

        # 볼트 타입 결정 (특수 용도 고려)
        bolt_type = fast_type.get("type", "UNKNOWN") if isinstance(fast_type, dict) else str(fast_type)
        special_apps = res.get("special_applications", {}).get("detected_applications", [])
        if "LT" in special_apps: bolt_type = "LT_BOLT"
        elif "PSV" in special_apps: bolt_type = "PSV_BOLT"

        # 코팅 타입
        desc_upper = data["original_description"].upper()
        coating = "UNKNOWN"
        if "GALV" in desc_upper: coating = "GALVANIZED"
        elif "ZINC" in desc_upper: coating = "ZINC_PLATED"

        db.execute(text("""
            INSERT INTO bolt_details (
                material_id, file_id, bolt_type, thread_type,
                diameter, length, material_grade, coating_type
            ) VALUES (:mid, :fid, :type, :thread, :dia, :len, :grade, :coating)
        """), {
            "mid": mid, "fid": fid,
            "type": bolt_type,
            "thread": res.get("thread_specification", {}).get("standard", "UNKNOWN") if isinstance(res.get("thread_specification"), dict) else "UNKNOWN",
            "dia": dim_info.get("nominal_size", data.get("main_nom", "")),
            "len": dim_info.get("length", ""),
            "grade": mat_info.get("grade", "") if isinstance(mat_info, dict) else "",
            "coating": coating
        })

    @staticmethod
    def _save_valve_details(db, mid, fid, res, data):
        val_type = res.get("valve_type", {})
        mat_info = res.get("material", {})
        
        if mat_info.get("grade"):
            db.execute(text("UPDATE materials SET material_grade = :g WHERE id = :id"), 
                      {"g": mat_info.get("grade"), "id": mid})

        db.execute(text("""
            INSERT INTO valve_details (
                material_id, file_id, valve_type, connection_method, 
                pressure_rating, body_material, size_inches
            ) VALUES (:mid, :fid, :type, :conn, :rating, :body, :size)
        """), {
            "mid": mid, "fid": fid,
            "type": val_type.get("type", "UNKNOWN") if isinstance(val_type, dict) else str(val_type),
            "conn": res.get("connection_method", {}).get("method", "UNKNOWN") if isinstance(res.get("connection_method"), dict) else "UNKNOWN",
            "rating": res.get("pressure_rating", {}).get("rating", "UNKNOWN") if isinstance(res.get("pressure_rating"), dict) else "UNKNOWN",
            "body": mat_info.get("grade", "") if isinstance(mat_info, dict) else "",
            "size": data.get("main_nom") or data.get("size_spec")
        })

    @staticmethod
    def _save_gasket_details(db, mid, fid, res, data):
        gask_type = res.get("gasket_type", {})
        
        db.execute(text("""
            INSERT INTO gasket_details (
                material_id, file_id, gasket_type, pressure_rating, size_inches
            ) VALUES (:mid, :fid, :type, :rating, :size)
        """), {
            "mid": mid, "fid": fid,
            "type": gask_type.get("type", "UNKNOWN") if isinstance(gask_type, dict) else str(gask_type),
            "rating": res.get("pressure_rating", {}).get("rating", "UNKNOWN") if isinstance(res.get("pressure_rating"), dict) else "UNKNOWN",
            "size": data.get("main_nom") or data.get("size_spec")
        })

    @staticmethod
    def _save_support_details(db, mid, fid, res, data):
        db.execute(text("""
            INSERT INTO support_details (
                material_id, file_id, support_type, pipe_size
            ) VALUES (:mid, :fid, :type, :size)
        """), {
            "mid": mid, "fid": fid,
            "type": res.get("support_type", "UNKNOWN"),
            "size": res.get("size_info", {}).get("pipe_size", "")
        })
