"""
Excel file parser for KPI Analysis
Parses KPI Excel files with summary sheet, achievement sheet, and detail sheets
"""

import pandas as pd
import numpy as np
from typing import Dict, List, Any, Optional, Tuple
from pathlib import Path
import re
from datetime import datetime
import logging

from ..models.kpi_models import (
    KPIFile, KPISummary, AchievementSheet, AchievementItem,
    KPISheet, KPIPeriodData, KpiCategory, PolarizationType, KpiStatus
)
from config.settings import settings

logger = logging.getLogger(__name__)

class ExcelParser:
    """Excel file parser for KPI data"""
    
    def __init__(self):
        self.supported_sheets = [
            'KPI', 'Achievement', 'F2a', 'F2b', 'C2a', 'C2b',
            'B1a', 'B1b', 'B1c', 'B1d', 'B1e', 'B1f', 'B2a', 'B3a',
            'L1a', 'L1b', 'L2a', 'Kumul max', 'Kumul min', 'Fix max', 'Fix min'
        ]
    
    async def parse_excel_file(self, file_path: str) -> KPIFile:
        """Parse complete KPI Excel file"""
        try:
            logger.info(f"Starting to parse Excel file: {file_path}")
            excel_file = pd.ExcelFile(file_path)
            logger.info(f"Excel file loaded. Available sheets: {excel_file.sheet_names}")
            
            # Parse main components
            summary = self._parse_summary_sheet(excel_file)
            logger.info(f"Summary parsed: {summary.name}, Score: {summary.final_score}")
            
            achievements = self._parse_achievement_sheet(excel_file)
            logger.info(f"Achievements parsed: {len(achievements.items)} items")
            
            kpi_sheets = self._parse_kpi_detail_sheets(excel_file)
            logger.info(f"KPI sheets parsed: {len(kpi_sheets)} sheets")
            
            # Calculate perspective scores and achievement rate
            perspective_scores = self._calculate_perspective_scores(kpi_sheets)
            logger.info(f"Perspective scores calculated: {perspective_scores}")
            
            achievement_rate = self._calculate_achievement_rate(achievements)
            logger.info(f"Achievement rate calculated: {achievement_rate}%")
            
            kpi_file = KPIFile(
                filename=Path(file_path).name,
                upload_date=datetime.now(),
                file_path=file_path,
                summary=summary,
                achievements=achievements,
                kpi_sheets=kpi_sheets,
                perspective_scores=perspective_scores,
                achievement_rate=achievement_rate
            )
            
            logger.info(f"Excel file parsing completed successfully")
            return kpi_file
            
        except Exception as e:
            logger.error(f"Error parsing Excel file {file_path}: {str(e)}", exc_info=True)
            raise ValueError(f"Failed to parse Excel file: {str(e)}")
    
    def _parse_summary_sheet(self, excel_file: pd.ExcelFile) -> KPISummary:
        """Parse the main KPI summary sheet"""
        try:
            df = pd.read_excel(excel_file, sheet_name='KPI', header=None)
            
            # Extract basic information based on actual Excel structure
            # Row 1 (index 1): "Jabatan : Information Technology Manager" - this is the POSITION
            job_title_cell = self._extract_cell_value(df, 1, 1)  # B2
            position = "Manager"  # Default
            if job_title_cell and ":" in job_title_cell:
                position = job_title_cell.split(":", 1)[1].strip()  # This is the position (e.g., "Information Technology Manager")
            
            # Row 4 (index 4): Name is in column 3 (D column)
            name = self._extract_cell_value(df, 4, 3)  # D5: "Suherdy Yacob"
            
            # Row 3 (index 3): Department is in column 12 (M column)
            department = self._extract_cell_value(df, 3, 12)  # M4: "Information Technology"
            
            # Row 4 (index 4): Supervisor is in column 12 (M column)
            supervisor_info = self._extract_cell_value(df, 4, 12)  # M5: "Robertus Haryo / GM Business Support"
            
            # Row 5 (index 5): Performance period is in column 12 (M column)
            performance_period = self._extract_cell_value(df, 5, 12)  # M6: "2025"
            
            # Parse name (already clean)
            person_name = name.strip() if name else "Unknown"
            
            # Use department as job_title for the summary
            job_title = department if department else position
            
            # Parse supervisor name and position
            supervisor_name = ""
            supervisor_position = ""
            if supervisor_info and "/" in supervisor_info:
                sup_parts = supervisor_info.split("/", 1)
                supervisor_name = sup_parts[0].strip()
                supervisor_position = sup_parts[1].strip() if len(sup_parts) > 1 else ""
            elif supervisor_info:
                supervisor_name = supervisor_info.strip()
                supervisor_position = ""
            
            # Extract scores - look for "Total %" row or the last row with scores
            total_score = 0.0
            total_weight = 100.0
            
            # Method 1: Find the row with "Total %" in column 1 (B column)
            found_total = False
            for idx, row in df.iterrows():
                cell_value = self._safe_str(row.iloc[1]) if len(row) > 1 else ""  # Column B (index 1)
                if "Total" in cell_value and "%" in cell_value:
                    # The total weight (Bobot) is in column 17 (R column)
                    if len(row) > 17:
                        weight_value = self._extract_numeric_value(row, 17)
                        if weight_value is not None and weight_value > 0:
                            total_weight = weight_value * 100  # Convert to percentage if needed
                            logger.info(f"Found total weight: {total_weight} at row {idx}, column 17")
                    
                    # The total score is in column 18 (S column) - "Total (Skor * Bobot)"
                    if len(row) > 18:
                        score_value = self._extract_numeric_value(row, 18)
                        if score_value is not None:
                            total_score = score_value
                            logger.info(f"Found total score: {total_score} at row {idx}, column 18")
                            found_total = True
                    break
            
            # Method 2: If not found, look for the last row with a value in column 18 that looks like a total
            # (usually after all KPI rows, column 17 has ~1.0 and column 18 has the total score)
            if not found_total:
                for idx in range(len(df) - 1, -1, -1):
                    row = df.iloc[idx]
                    if len(row) > 18:
                        score_value = self._extract_numeric_value(row, 18)
                        weight_value = self._extract_numeric_value(row, 17) if len(row) > 17 else None
                        
                        # Check if this looks like a total row (weight ~1.0 and score > 0)
                        if (score_value is not None and score_value > 0 and 
                            weight_value is not None and 0.9 <= weight_value <= 1.1):
                            total_score = score_value
                            total_weight = weight_value * 100  # Convert to percentage
                            logger.info(f"Found total score (method 2): {total_score} at row {idx}, column 18")
                            logger.info(f"Found total weight (method 2): {total_weight} at row {idx}, column 17")
                            break
            
            # Final score is the same as total score (in points, not percentage)
            final_score = total_score
            
            return KPISummary(
                job_title=job_title or "Department",  # Department (e.g., "Information Technology")
                name=person_name,
                position=position,  # Position (e.g., "Information Technology Manager")
                supervisor_name=supervisor_name,
                supervisor_position=supervisor_position,
                join_date=None,
                performance_period=performance_period or "2025",
                total_score=total_score,
                total_weight=total_weight,
                final_score=final_score
            )
            
        except Exception as e:
            logger.error(f"Error parsing summary sheet: {str(e)}", exc_info=True)
            # Return default summary if parsing fails
            return KPISummary(
                job_title="Manager",
                name="Unknown",
                position="Manager",
                supervisor_name="",
                supervisor_position="",
                performance_period="2025",
                total_score=0.0,
                total_weight=100.0,
                final_score=0.0
            )
    
    def _parse_achievement_sheet(self, excel_file: pd.ExcelFile) -> AchievementSheet:
        """Parse achievement status sheet"""
        try:
            df = pd.read_excel(excel_file, sheet_name='Achievement', header=None)
            
            items = []
            
            # Find header row
            header_row_idx = None
            for idx, row in df.iterrows():
                for cell in row:
                    if pd.notna(cell) and 'Kode BSC' in str(cell):
                        header_row_idx = idx
                        break
                if header_row_idx is not None:
                    break
            
            if header_row_idx is None:
                logger.warning("Could not find header row in Achievement sheet")
                return AchievementSheet(items=[])
            
            # Parse data rows
            for idx in range(header_row_idx + 1, len(df)):
                row = df.iloc[idx]
                
                # Extract values from columns (adjust indices based on actual structure)
                code = self._safe_str(row.iloc[1]) if len(row) > 1 else ""  # Column B
                indicator = self._safe_str(row.iloc[2]) if len(row) > 2 else ""  # Column C
                status_str = self._safe_str(row.iloc[3]) if len(row) > 3 else ""  # Column D
                description = self._safe_str(row.iloc[4]) if len(row) > 4 else ""  # Column E
                
                if code and indicator and code != 'Kode BSC':
                    # Map status string to enum
                    status = KpiStatus.NOT_ACHIEVE
                    if 'achieve' in status_str.lower() and 'not' not in status_str.lower():
                        status = KpiStatus.ACHIEVE
                    elif 'not achieve' in status_str.lower():
                        status = KpiStatus.NOT_ACHIEVE
                    elif 'data' in description.lower() or 'isi' in description.lower() or 'data' in status_str.lower():
                        status = KpiStatus.NO_DATA
                    
                    items.append(AchievementItem(
                        code=code,
                        indicator=indicator,
                        status=status,
                        description=description
                    ))
            
            logger.info(f"Parsed {len(items)} achievement items")
            return AchievementSheet(items=items)
            
        except Exception as e:
            logger.error(f"Error parsing achievement sheet: {str(e)}", exc_info=True)
            return AchievementSheet(items=[])
    
    def _parse_kpi_detail_sheets(self, excel_file: pd.ExcelFile) -> List[KPISheet]:
        """Parse KPI detail sheets"""
        kpi_sheets = []
        
        for sheet_name in excel_file.sheet_names:
            if sheet_name in ['KPI', 'Achievement']:
                continue
                
            try:
                kpi_sheet = self._parse_single_kpi_sheet(excel_file, sheet_name)
                if kpi_sheet:
                    kpi_sheets.append(kpi_sheet)
            except Exception as e:
                logger.warning(f"Error parsing sheet {sheet_name}: {str(e)}")
                continue
        
        return kpi_sheets
    
    def _parse_single_kpi_sheet(self, excel_file: pd.ExcelFile, sheet_name: str) -> Optional[KPISheet]:
        """Parse a single KPI detail sheet"""
        try:
            df = pd.read_excel(excel_file, sheet_name=sheet_name, header=None)
            logger.debug(f"Parsing sheet: {sheet_name}, shape: {df.shape}")
            
            # Extract metadata from the top of the sheet
            # Row 0: "Nama KPI" in col 0, unit in col 1, actual name in col 2
            name = self._extract_cell_value(df, 0, 2) or sheet_name  # Column C (index 2) has the actual name
            
            # Determine category from sheet name pattern
            category = self._determine_category(sheet_name)
            polarization = self._determine_polarization(df)
            period = self._determine_period(df)
            unit = self._extract_unit(df)
            
            # Extract target values
            target_value, threshold_min, threshold_max = self._extract_target_values(df)
            
            # Parse period data
            period_data = self._parse_period_data(df)
            
            if not period_data:
                logger.warning(f"No period data found for sheet {sheet_name}")
                return None
            
            logger.debug(f"Sheet {sheet_name} parsed successfully with {len(period_data)} periods")
            
            return KPISheet(
                name=name,
                category=category,
                code=sheet_name,
                polarization=polarization,
                period=period,
                unit=unit,
                target_value=target_value,
                threshold_min=threshold_min,
                threshold_max=threshold_max,
                period_data=period_data
            )
            
        except Exception as e:
            logger.error(f"Error parsing KPI sheet {sheet_name}: {str(e)}", exc_info=True)
            return None
    
    def _parse_period_data(self, df: pd.DataFrame) -> List[KPIPeriodData]:
        """Parse period data from KPI sheet"""
        period_data = []
        
        # Find the header row with "Periode", "Realisasi", "Target", etc.
        header_row_idx = None
        for idx, row in df.iterrows():
            for cell in row:
                if pd.notna(cell) and 'Periode' in str(cell) and 'Realisasi' in str(df.iloc[idx].values):
                    header_row_idx = idx
                    break
            if header_row_idx is not None:
                break
        
        if header_row_idx is None:
            logger.warning("Could not find data header row")
            return []
        
        # Parse data rows after header
        for idx in range(header_row_idx + 1, len(df)):
            row = df.iloc[idx]
            
            # Check if this is a valid data row
            period_name = self._safe_str(row.iloc[0]) if len(row) > 0 else ""
            
            # Skip empty rows or total rows
            if not period_name or period_name.lower() in ['total', 'jumlah', '', 'nan']:
                continue
            
            # Skip if period name is not a month name
            months = ['januari', 'februari', 'maret', 'april', 'mei', 'juni', 
                     'juli', 'agustus', 'september', 'oktober', 'november', 'desember',
                     'january', 'february', 'march', 'may', 'june', 'july', 
                     'august', 'october', 'q1', 'q2', 'q3', 'q4', 'semester']
            if not any(month in period_name.lower() for month in months):
                continue
            
            # Extract data values based on column positions
            realization = self._extract_numeric_value(row, 1)
            target = self._extract_numeric_value(row, 2)
            threshold_min = self._extract_numeric_value(row, 3)
            score = self._extract_numeric_value(row, 4)
            status = self._safe_str(row.iloc[5]) if len(row) > 5 else ""
            
            period_data.append(KPIPeriodData(
                period=period_name,
                realization=realization,
                target=target,
                threshold_min=threshold_min,
                threshold_max=None,
                score=score,
                status=status,
                notes=""
            ))
        
        return period_data
    
    def _determine_category(self, sheet_name: str) -> KpiCategory:
        """Determine KPI category from sheet name"""
        if sheet_name.startswith('F'):
            return KpiCategory.FINANCIAL
        elif sheet_name.startswith('C'):
            return KpiCategory.CUSTOMER
        elif sheet_name.startswith('B'):
            return KpiCategory.INTERNAL_BUSINESS_PROCESS
        elif sheet_name.startswith('L'):
            return KpiCategory.LEARNING_GROWTH
        else:
            return KpiCategory.FINANCIAL  # Default
    
    def _determine_polarization(self, df: pd.DataFrame) -> PolarizationType:
        """Determine polarization type from sheet content"""
        for _, row in df.iterrows():
            for cell in row:
                if pd.notna(cell) and isinstance(cell, str):
                    if 'minimal' in cell.lower():
                        return PolarizationType.MINIMAL
                    elif 'maksimal' in cell.lower() or 'maximal' in cell.lower():
                        return PolarizationType.MAKSIMAL
        return PolarizationType.MINIMAL  # Default
    
    def _determine_period(self, df: pd.DataFrame) -> str:
        """Determine period type from sheet content"""
        for _, row in df.iterrows():
            for cell in row:
                if pd.notna(cell) and isinstance(cell, str):
                    cell_lower = cell.lower()
                    if 'bulanan' in cell_lower or 'monthly' in cell_lower:
                        return 'Monthly'
                    elif 'quarter' in cell_lower or 'kuarter' in cell_lower:
                        return 'Quarterly'
                    elif 'semester' in cell_lower:
                        return 'Semi-Annual'
                    elif 'tahunan' in cell_lower or 'annual' in cell_lower:
                        return 'Annual'
        return 'Monthly'  # Default
    
    def _extract_unit(self, df: pd.DataFrame) -> Optional[str]:
        """Extract unit of measurement from column 1 (B column)"""
        # The unit is typically in row 0, column 1 (B1)
        if len(df) > 0 and len(df.columns) > 1:
            unit_cell = self._safe_str(df.iloc[0, 1])
            if unit_cell:
                # Return the unit as-is (%, #, etc.)
                return unit_cell
        
        # Fallback: search for unit in the sheet
        for _, row in df.iterrows():
            for cell in row:
                if pd.notna(cell) and isinstance(cell, str):
                    cell_str = cell.strip()
                    if cell_str in ['%', '#', 'IDR', 'Jam', 'Hari']:
                        return cell_str
        return None
    
    def _extract_target_values(self, df: pd.DataFrame) -> Tuple[Optional[float], Optional[float], Optional[float]]:
        """Extract target and threshold values"""
        target_value = None
        threshold_min = None
        threshold_max = None
        
        for _, row in df.iterrows():
            for col_idx, cell in enumerate(row):
                if pd.notna(cell) and isinstance(cell, str):
                    cell_str = cell.lower()
                    if 'target' in cell_str or 'target (100%)' in cell_str:
                        target_value = self._find_numeric_value(df, _, col_idx + 1)
                    elif 'threshold' in cell_str or 'treshold' in cell_str:
                        threshold_min = self._find_numeric_value(df, _, col_idx + 1)
        
        return target_value, threshold_min, threshold_max
    
    def _calculate_perspective_scores(self, kpi_sheets: List[KPISheet]) -> Dict[KpiCategory, float]:
        """Calculate scores for each perspective"""
        perspective_scores = {}
        
        for category in KpiCategory:
            category_sheets = [sheet for sheet in kpi_sheets if sheet.category == category]
            
            if not category_sheets:
                perspective_scores[category] = 0.0
                continue
            
            total_score = 0.0
            total_weight = 0.0
            
            for sheet in category_sheets:
                # Use average score from period data
                if sheet.period_data:
                    scores = [pd.score for pd in sheet.period_data if pd.score is not None]
                    if scores:
                        avg_score = sum(scores) / len(scores)
                        total_score += avg_score
                        total_weight += 1.0
            
            perspective_scores[category] = total_score / total_weight if total_weight > 0 else 0.0
        
        return perspective_scores
    
    def _calculate_achievement_rate(self, achievements: AchievementSheet) -> float:
        """Calculate overall achievement rate"""
        if not achievements.items:
            return 0.0
        
        achieved = sum(1 for item in achievements.items if item.status == KpiStatus.ACHIEVE)
        total = len(achievements.items)
        
        return (achieved / total) * 100 if total > 0 else 0.0
    
    # Helper methods
    def _extract_cell_value(self, df: pd.DataFrame, row: int, col: int) -> Optional[str]:
        """Safely extract cell value"""
        try:
            if row < len(df) and col < len(df.columns):
                cell_value = df.iloc[row, col]
                return self._safe_str(cell_value)
        except:
            pass
        return None
    
    def _safe_str(self, value) -> str:
        """Safely convert value to string"""
        if pd.isna(value) or value is None:
            return ""
        return str(value).strip()
    
    def _extract_numeric_value(self, row: pd.Series, col_idx: int) -> Optional[float]:
        """Extract numeric value from cell"""
        try:
            if col_idx < len(row):
                value = row.iloc[col_idx]
                if pd.notna(value) and isinstance(value, (int, float)):
                    return float(value)
                elif pd.notna(value) and isinstance(value, str):
                    # Try to extract number from string
                    import re
                    numbers = re.findall(r'\d+\.?\d*', value)
                    if numbers:
                        return float(numbers[0])
        except:
            pass
        return None
    
    def _find_numeric_value(self, df: pd.DataFrame, row: int, col: int) -> Optional[float]:
        """Find numeric value in DataFrame"""
        return self._extract_numeric_value(df.iloc[row], col)
    
    def _find_data_start_row(self, df: pd.DataFrame) -> int:
        """Find where the data table starts"""
        # Look for headers like "Periode", "Realisasi", "Target"
        for idx, row in df.iterrows():
            for col_idx, cell in enumerate(row):
                if pd.notna(cell) and isinstance(cell, str):
                    cell_lower = cell.lower()
                    if 'periode' in cell_lower and 'realisasi' in str(row.values).lower():
                        return idx + 1
        return 6  # Default start row (after typical header rows)

# Global Excel parser instance
excel_parser = ExcelParser()