import logging

import pandas as pd
import re
from typing import List, Dict, Optional
import uuid
from datetime import datetime
from pathlib import Path

logger = logging.getLogger(__name__)

# 허용된 확장자
ALLOWED_EXTENSIONS = {".xlsx", ".xls", ".csv"}

class BOMParser:
    """BOM 파일 파싱을 담당하는 클래스"""
    
    @staticmethod
    def validate_extension(filename: str) -> bool:
        return Path(filename).suffix.lower() in ALLOWED_EXTENSIONS

    @staticmethod
    def generate_unique_filename(original_filename: str) -> str:
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        unique_id = str(uuid.uuid4())[:8]
        stem = Path(original_filename).stem
        suffix = Path(original_filename).suffix
        return f"{stem}_{timestamp}_{unique_id}{suffix}"

    @staticmethod
    def detect_format(df: pd.DataFrame) -> str:
        """
        엑셀 헤더를 분석하여 양식을 감지합니다.
        
        Returns:
            'INVENTOR': 인벤터 추출 양식 (NO., NAME, Q'ty, LENGTH & THICKNESS...)
            'STANDARD': 기존 표준/퍼지 매핑 엑셀 (DWG_NAME, LINE_NUM, MAIN_NOM...)
        """
        columns = [str(c).strip().upper() for c in df.columns]
        
        # 인벤터 양식 특징 (오타 포함)
        INVENTOR_KEYWORDS = ['LENGTH & THICKNESS', 'DESCIPTION', "Q'TY"]
        
        for keyword in INVENTOR_KEYWORDS:
            if any(keyword in col for col in columns):
                return 'INVENTOR'
        
        # 표준 양식 특징
        STANDARD_KEYWORDS = ['MAIN_NOM', 'DWG_NAME', 'LINE_NUM']
        for keyword in STANDARD_KEYWORDS:
            if any(keyword in col for col in columns):
                return 'STANDARD'
                
        return 'STANDARD'  # 기본값

    @classmethod
    def parse_file(cls, file_path: str) -> List[Dict]:
        """파일 경로를 받아 적절한 파서를 통해 데이터를 추출합니다."""
        file_extension = Path(file_path).suffix.lower()
        
        try:
            if file_extension == ".csv":
                df = pd.read_csv(file_path, encoding='utf-8')
            elif file_extension in [".xlsx", ".xls"]:
                # xlrd 엔진 명시 (xls 지원)
                if file_extension == ".xls":
                    df = pd.read_excel(file_path, sheet_name=0, engine='xlrd')
                else:
                    df = pd.read_excel(file_path, sheet_name=0, engine='openpyxl')
            else:
                raise ValueError("지원하지 않는 파일 형식")
            
            # 데이터프레임 전처리 (빈 행 제거 등)
            df = df.dropna(how='all')
            
            # 양식 감지
            format_type = cls.detect_format(df)
            logger.info(f"감지된 BOM 양식: {format_type}")
            
            if format_type == 'INVENTOR':
                return cls._parse_inventor_bom(df)
            else:
                return cls._parse_standard_bom(df)
                
        except Exception as e:
            raise ValueError(f"파일 파싱 실패: {str(e)}")

    @staticmethod
    def _parse_standard_bom(df: pd.DataFrame) -> List[Dict]:
        """기존의 퍼지 매핑 방식 파서 (표준 양식)"""
        # 컬럼명 전처리
        df.columns = df.columns.str.strip().str.upper()
        
        # 대소문자 구분 없는 매핑을 위해 컬럼명 대문자화 사용
        column_mapping = {
            'description': ['DESCRIPTION', 'ITEM', 'MATERIAL', '품명', '자재명', 'SPECIFICATION'],
            'quantity': ['QTY', 'QUANTITY', 'EA', '수량', 'AMOUNT'],
            'main_size': ['MAIN_NOM', 'NOMINAL_DIAMETER', 'ND', '주배관', 'SIZE'],
            'red_size': ['RED_NOM', 'REDUCED_DIAMETER', '축소배관'],
            'length': ['LENGTH', 'LEN', '길이'],
            'weight': ['WEIGHT', 'WT', '중량'],
            'dwg_name': ['DWG_NAME', 'DRAWING', '도면명', '도면번호'],
            'line_num': ['LINE_NUM', 'LINE_NUMBER', '라인번호']
        }
        
        mapped_columns = {}
        for standard_col, possible_names in column_mapping.items():
            for possible_name in possible_names:
                # 대문자로 비교
                possible_upper = possible_name.upper()
                if possible_upper in df.columns:
                    mapped_columns[standard_col] = possible_upper
                    break
        
        logger.info(f"[Standard] 컬럼 매핑 결과: {mapped_columns}")
        
        materials = []
        for index, row in df.iterrows():
            description = str(row.get(mapped_columns.get('description', ''), ''))
            
            # 제외 항목 처리
            description_upper = description.upper()
            if ('WELD GAP' in description_upper or 'WELDING GAP' in description_upper or 
                '웰드갭' in description_upper or '용접갭' in description_upper):
                continue
            
            # 수량 처리
            quantity_raw = row.get(mapped_columns.get('quantity', ''), 0)
            try:
                quantity = float(quantity_raw) if pd.notna(quantity_raw) else 0
            except:
                quantity = 0
            
            # 재질 등급 추출 (ASTM)
            material_grade = ""
            if "ASTM" in description_upper:
                astm_match = re.search(r'ASTM\s+(A\d{3,4}[A-Z]*(?:\s+(?:GR\s+)?[A-Z0-9]+)?)', description_upper)
                if astm_match:
                    material_grade = astm_match.group(0).strip()
            
            # 사이즈 처리
            main_size = str(row.get(mapped_columns.get('main_size', ''), ''))
            red_size = str(row.get(mapped_columns.get('red_size', ''), ''))
            
            main_nom = main_size if main_size != 'nan' and main_size != '' else None
            red_nom = red_size if red_size != 'nan' and red_size != '' else None
            
            if main_size != 'nan' and red_size != 'nan' and red_size != '':
                size_spec = f"{main_size} x {red_size}"
            elif main_size != 'nan' and main_size != '':
                size_spec = main_size
            else:
                size_spec = ""
            
            # 길이 처리
            length_raw = row.get(mapped_columns.get('length', ''), '')
            length_value = None
            if pd.notna(length_raw) and str(length_raw).strip() != '':
                try:
                    length_value = float(str(length_raw).strip())
                except:
                    length_value = None
            
            # 도면/라인 번호
            dwg_name = row.get(mapped_columns.get('dwg_name', ''), '')
            dwg_name = str(dwg_name).strip() if pd.notna(dwg_name) and str(dwg_name).strip() not in ['', 'nan', 'None'] else None
            
            line_num = row.get(mapped_columns.get('line_num', ''), '')
            line_num = str(line_num).strip() if pd.notna(line_num) and str(line_num).strip() not in ['', 'nan', 'None'] else None
            
            if description and description not in ['nan', 'None', '']:
                materials.append({
                    'original_description': description,
                    'quantity': quantity,
                    'unit': "EA",
                    'size_spec': size_spec,
                    'main_nom': main_nom,
                    'red_nom': red_nom,
                    'material_grade': material_grade,
                    'length': length_value,
                    'dwg_name': dwg_name,
                    'line_num': line_num,
                    'line_number': index + 1,
                    'row_number': index + 1
                })
        
        return materials

    @staticmethod
    def _parse_inventor_bom(df: pd.DataFrame) -> List[Dict]:
        """
        [신규] 인벤터 추출 양식 파서
        헤더: NO., NAME, Q'ty, LENGTH & THICKNESS, WEIGHT, DESCIPTION, REMARK
        특징: Size 컬럼 부재, NAME에 주요 정보 포함
        """
        logger.warning("[Inventor] 인벤터 양식 파서를 사용합니다.")
        
        # 컬럼명 전처리 (좌우 공백 제거 및 대문자화)
        df.columns = df.columns.str.strip().str.upper()
        
        # 인벤터 전용 매핑
        col_name = 'NAME'
        col_qty = "Q'TY"
        col_desc = 'DESCIPTION'  # 오타 그대로 반영
        col_remark = 'REMARK'
        col_length = 'LENGTH & THICKNESS' # 길이 정보가 여기 있을 수 있음
        
        materials = []
        for index, row in df.iterrows():
            # 1. 품명 (NAME 컬럼 우선 사용)
            name_val = str(row.get(col_name, '')).strip()
            desc_val = str(row.get(col_desc, '')).strip()
            
            # NAME과 DESCIPTION 병합 (필요시)
            # 보통 인벤터에서 NAME이 'ADAPTER OD 1/4" x NPT(F) 1/2"' 같이 상세 스펙
            # DESCIPTION은 'SS-400-7-8' 같은 자재 코드일 수 있음
            # 일단 NAME을 메인 description으로 사용하고, DESCIPTION을 괄호에 추가
            if desc_val and desc_val not in ['nan', 'None', '']:
                full_description = f"{name_val} ({desc_val})"
            else:
                full_description = name_val
            
            if not full_description or full_description in ['nan', 'None', '']:
                continue

            # 2. 수량
            qty_raw = row.get(col_qty, 0)
            try:
                quantity = float(qty_raw) if pd.notna(qty_raw) else 0
            except:
                quantity = 0

            # 3. 사이즈 추출 (NAME 컬럼 분석)
            # 패턴: 1/2", 1/4", 100A, 50A, 10x20 등
            size_spec = ""
            main_nom = None
            red_nom = None
            
            # 인치/MM 사이즈 추출 시도
            # 예: "ADAPTER OD 1/4" x NPT(F) 1/2"" -> 1/4" x 1/2"
            # 예: "ELBOW 90D 100A" -> 100A
            
            # 인치 패턴 (1/2", 3/4" 등)
            inch_sizes = re.findall(r'(\d+(?:/\d+)?)"', name_val)
            # A단위 패턴 (100A, 50A 등)
            a_sizes = re.findall(r'(\d+)A', name_val)
            
            if inch_sizes:
                if len(inch_sizes) >= 2:
                    main_nom = f'{inch_sizes[0]}"'
                    red_nom = f'{inch_sizes[1]}"'
                    size_spec = f'{main_nom} x {red_nom}'
                else:
                    main_nom = f'{inch_sizes[0]}"'
                    size_spec = main_nom
            elif a_sizes:
                if len(a_sizes) >= 2:
                    main_nom = f'{a_sizes[0]}A'
                    red_nom = f'{a_sizes[1]}A'
                    size_spec = f'{main_nom} x {red_nom}'
                else:
                    main_nom = f'{a_sizes[0]}A'
                    size_spec = main_nom
            
            # 4. 재질 정보
            material_grade = ""
            # NAME이나 DESCIPTION에서 재질 키워드 찾기 (SS, SUS, A105 등)
            combined_text = (full_description + " " + desc_val).upper()
            if "SUS" in combined_text or "SS" in combined_text:
                if "304" in combined_text: material_grade = "SUS304"
                elif "316" in combined_text: material_grade = "SUS316"
                else: material_grade = "SUS"
            elif "A105" in combined_text:
                material_grade = "A105"
            
            # 5. 길이 정보
            length_value = None
            length_raw = row.get(col_length, '')
            # 값이 있고 숫자로 변환 가능하면 사용
            if pd.notna(length_raw) and str(length_raw).strip():
                try:
                    # '100 mm' 등의 형식 처리 필요할 수 있음
                    length_str = str(length_raw).lower().replace('mm', '').strip()
                    length_value = float(length_str)
                except:
                    pass

            materials.append({
                'original_description': full_description,
                'quantity': quantity,
                'unit': "EA",
                'size_spec': size_spec,
                'main_nom': main_nom,
                'red_nom': red_nom,
                'material_grade': material_grade,
                'length': length_value,
                'dwg_name': None, # 인벤터 파일엔 도면명 컬럼이 없음
                'line_num': None,
                'line_number': index + 1,
                'row_number': index + 1
            })
            
        return materials
