"""
日期轴 XML 直接操作模块

专门用于设置日期轴的底层 XML 属性，绕过 python-pptx 的限制。

使用场景：
- 横轴确定为日期类型
- 需要精确控制刻度密度
- 需要设置日期范围
"""

from lxml import etree
from typing import Optional, Literal
from datetime import datetime

from .oxml_ns import NAMESPACES


def excel_date(date: datetime) -> float:
    """
    将 Python datetime 转换为 Excel 日期序号
    
    Excel 的日期基准：1900-01-01 = 1
    """
    base_date = datetime(1899, 12, 30)  # Excel 的实际基准日期
    delta = date - base_date
    return float(delta.days)


class DateAxisConfig:
    """
    日期轴 XML 配置类
    
    直接操作 XML 以实现完整的日期轴控制
    """
    
    def __init__(
        self,
        base_unit: Literal["days", "months", "years"] = "days",
        major_unit: float = 7.0,
        major_unit_scale: Literal["days", "months", "years"] = "days",
        minor_unit: Optional[float] = None,
        minor_unit_scale: Literal["days", "months", "years"] = "days",
        min_date: Optional[datetime] = None,
        max_date: Optional[datetime] = None,
        number_format: str = "yyyy-mm-dd",
        auto_adjust: bool = False,
    ):
        """
        初始化日期轴配置
        
        Args:
            base_unit: 基础时间单位（days/months/years）
            major_unit: 主刻度单位数值（如 7 表示每 7 个单位一个刻度）
            major_unit_scale: 主刻度单位类型
            minor_unit: 次刻度单位数值
            minor_unit_scale: 次刻度单位类型
            min_date: 最小日期（用于固定范围）
            max_date: 最大日期（用于固定范围）
            number_format: 日期显示格式
            auto_adjust: 是否允许 PowerPoint 自动调整
        
        Examples:
            >>> # 每周显示一个刻度
            >>> config = DateAxisConfig(
            ...     base_unit="days",
            ...     major_unit=7.0,
            ...     major_unit_scale="days",
            ... )
            
            >>> # 每月显示一个刻度
            >>> config = DateAxisConfig(
            ...     base_unit="months",
            ...     major_unit=1.0,
            ...     major_unit_scale="months",
            ... )
            
            >>> # 固定日期范围
            >>> config = DateAxisConfig(
            ...     base_unit="days",
            ...     major_unit=14.0,
            ...     min_date=datetime(2024, 1, 1),
            ...     max_date=datetime(2024, 12, 31),
            ... )
        """
        self.base_unit = base_unit
        self.major_unit = major_unit
        self.major_unit_scale = major_unit_scale
        self.minor_unit = minor_unit or (major_unit / 7)  # 默认为主刻度的 1/7
        self.minor_unit_scale = minor_unit_scale
        self.min_date = min_date
        self.max_date = max_date
        self.number_format = number_format
        self.auto_adjust = auto_adjust
    
    def apply_to_chart(self, chart):
        """
        应用日期轴配置到图表
        
        Args:
            chart: python-pptx 的 Chart 对象
        """
        print(f"\n📅 应用日期轴配置（XML 直接操作）:")
        
        try:
            # 1. 获取 XML 元素
            chart_element = chart._element
            
            # ⭐ 新方案：数据已在 plots.py 中格式化为字符串并使用 strCache
            print(f"  → 数据已格式化为字符串标签（如 '2024/01'），使用 strCache")
            
            # ⭐ 关键修复：查找catAx并转换为dateAx
            # PowerPoint 不允许在 catAx 中使用时间单位元素，必须使用 dateAx
            cat_ax_elements = chart_element.findall('.//{http://schemas.openxmlformats.org/drawingml/2006/chart}catAx')
            
            if not cat_ax_elements:
                print("  ⚠️ 未找到分类轴元素")
                return
            
            # ⭐ 新方案：不转换为 dateAx，保持 catAx
            # PowerPoint 对 dateAx 的时间单位元素支持不稳定
            # 改用 catAx + numRef/numCache + scaling(min/max) + numFmt(日期格式)
            # 这样更简单且兼容性更好
            
            cat_ax = cat_ax_elements[0]
            print(f"  ✅ 使用 catAx（分类轴）+ 格式化字符串标签")
            
            # ⭐ 设置标签位置为"低"（在坐标轴下方）
            self._set_tick_label_position(cat_ax, 'low')
            print(f"  - 标签位置: 低（坐标轴下方）")
            
            # ⭐ 设置标签间隔（控制显示数量不超过 7 个）
            # 使用 major_unit 作为间隔值（向下取整）
            label_interval = int(self.major_unit)
            self._set_label_interval(cat_ax, label_interval)
            print(f"  - 标签间隔: 每 {label_interval} 个点显示一个标签")
            
            # ⭐ 设置字体大小为 9pt
            try:
                category_axis = chart.category_axis
                from pptx.util import Pt
                category_axis.tick_labels.font.size = Pt(9)
                category_axis.tick_labels.font.name = "黑体"
                print(f"  - 横轴字体: 黑体 9pt")
            except Exception as e:
                print(f"  ⚠️ 字体设置失败: {e}")
            
            print(f"  ✅ 日期轴配置完成")
            
        except Exception as e:
            print(f"  ❌ 日期轴配置失败: {e}")
            import traceback
            traceback.print_exc()
    
    def _convert_cat_to_numcache(self, chart_element):
        """
        ⚠️ 备用方法：将分类数据从 strCache 转换为 numCache
        
        【重要】此方法已不再默认调用（自修复后）
        
        修复说明：
        - 在 api.py 修复后，python-pptx 会直接生成 numCache
        - 此方法仅在需要手动处理旧文件或特殊场景时使用
        - 如果未来遇到 strCache，此方法会将字符串日期转换为 Excel 序列号
        
        转换逻辑：
        - 从 strCache 读取 "YYYY-MM-DD" 格式的字符串
        - 调用 excel_date() 转换为 Excel 日期序列号（如 45567）
        - 写入 numCache，让 PowerPoint 正确识别为日期
        """
        from lxml import etree
        
        # 查找所有 c:cat 元素
        cat_elements = chart_element.findall('.//{http://schemas.openxmlformats.org/drawingml/2006/chart}cat')
        
        converted_count = 0
        for cat_elem in cat_elements:
            # 查找 strCache（直接子元素）
            str_cache = cat_elem.find('{http://schemas.openxmlformats.org/drawingml/2006/chart}strCache')
            if str_cache is None:
                continue
            
            # 提取数据点
            pt_count_elem = str_cache.find('{http://schemas.openxmlformats.org/drawingml/2006/chart}ptCount')
            pt_count = int(pt_count_elem.get('val')) if pt_count_elem is not None else 0
            
            pts = str_cache.findall('{http://schemas.openxmlformats.org/drawingml/2006/chart}pt')
            
            if not pts:
                continue
            
            # 创建新的 numCache 元素
            num_cache = etree.Element(f"{{{NAMESPACES['c']}}}numCache")
            
            # 添加格式代码
            format_code = etree.SubElement(num_cache, f"{{{NAMESPACES['c']}}}formatCode")
            format_code.text = self.number_format if self.number_format else "yyyy/mm"
            
            # 添加点计数
            new_pt_count = etree.SubElement(num_cache, f"{{{NAMESPACES['c']}}}ptCount")
            new_pt_count.set('val', str(pt_count))
            
            # 复制所有数据点
            for pt in pts:
                idx = pt.get('idx')
                v_elem = pt.find('{http://schemas.openxmlformats.org/drawingml/2006/chart}v')
                if v_elem is not None and v_elem.text:
                    # 创建新的 pt 元素
                    new_pt = etree.SubElement(num_cache, f"{{{NAMESPACES['c']}}}pt")
                    new_pt.set('idx', idx)
                    
                    # 添加值（保持数字格式）
                    new_v = etree.SubElement(new_pt, f"{{{NAMESPACES['c']}}}v")
                    new_v.text = v_elem.text
            
            # 获取 strCache 在父元素中的位置
            str_cache_index = list(cat_elem).index(str_cache)
            
            # 删除旧的 strCache
            cat_elem.remove(str_cache)
            
            # 在相同位置插入新的 numCache
            cat_elem.insert(str_cache_index, num_cache)
            converted_count += 1
        
        if converted_count > 0:
            print(f"  → 已将 {converted_count} 个分类数据转换为 numCache（数值格式）")
    
    def _convert_catax_to_dateax(self, chart_element, cat_ax):
        """
        将 catAx (分类轴) 转换为 dateAx (日期轴)
        
        这是关键修复！PowerPoint 不允许在 catAx 中使用时间单位元素（baseTimeUnit, majorTimeUnit 等）。
        必须将整个 catAx 元素替换为 dateAx。
        
        Args:
            chart_element: 图表的根 XML 元素
            cat_ax: 要转换的 catAx 元素
            
        Returns:
            转换后的 dateAx 元素
        """
        from lxml import etree
        
        # 创建新的 dateAx 元素
        date_ax = etree.Element(f"{{{NAMESPACES['c']}}}dateAx")
        
        # 复制 catAx 的所有子元素到 dateAx
        # 移除不兼容的元素（如 lblAlgn, lblOffset, noMultiLvlLbl）
        # 保留重要元素（如 numFmt, txPr 等）
        skip_elements = ['lblAlgn', 'lblOffset', 'noMultiLvlLbl']
        
        for child in cat_ax:
            tag_name = child.tag.split('}')[-1] if '}' in child.tag else child.tag
            if tag_name not in skip_elements:
                # 深度复制元素（包括所有子元素）
                new_child = etree.Element(child.tag, child.attrib)
                new_child.text = child.text
                new_child.tail = child.tail
                
                # 复制所有子元素
                for subchild in child:
                    new_child.append(subchild)
                
                date_ax.append(new_child)
        
        # 在父元素中替换 catAx 为 dateAx
        parent = cat_ax.getparent()
        if parent is not None:
            parent_list = list(parent)
            cat_ax_index = parent_list.index(cat_ax)
            parent.remove(cat_ax)
            parent.insert(cat_ax_index, date_ax)
        
        return date_ax
    
    def _set_or_update_element(self, parent, tag_name: str, value: str):
        """
        设置或更新 XML 元素
        
        Args:
            parent: 父元素
            tag_name: 标签名（不含命名空间）
            value: 属性值
        """
        full_tag = f"{{{NAMESPACES['c']}}}{tag_name}"
        
        # 查找现有元素
        existing = parent.find(f'.//c:{tag_name}', namespaces=NAMESPACES)
        
        if existing is not None:
            # 更新现有元素
            existing.set('val', value)
        else:
            # 创建新元素
            new_element = etree.Element(full_tag)
            new_element.set('val', value)
            
            # ⭐ 关键修复：时间单位元素必须在最后（auto 之后）
            # OOXML 规范的 dateAx 元素顺序：
            # axId → scaling → delete → axPos → majorTickMark → minorTickMark → tickLblPos → numFmt → crossAx → crosses → auto → [时间单位]
            
            # 策略：在 auto 之后插入，如果没有 auto 则追加到最后
            auto_elements = parent.findall('.//c:auto', namespaces=NAMESPACES)
            if auto_elements and len(auto_elements) > 0:
                # 在 auto 之后插入
                auto_elem = auto_elements[0]
                parent_list = list(parent)
                auto_index = parent_list.index(auto_elem)
                parent.insert(auto_index + 1, new_element)
            else:
                # 如果没有 auto，追加到最后
                parent.append(new_element)
    
    def _set_date_range(self, cat_ax):
        """设置日期范围（最小值/最大值）"""
        # 查找或创建 scaling 元素
        scaling = cat_ax.find('.//c:scaling', namespaces=NAMESPACES)
        
        if scaling is None:
            scaling = etree.Element(f"{{{NAMESPACES['c']}}}scaling")
            # 插入到合适位置（通常在 axId 之前）
            ax_id_elements = cat_ax.findall('.//c:axId', namespaces=NAMESPACES)
            if ax_id_elements:
                ax_id_elements[0].addprevious(scaling)
            else:
                cat_ax.insert(0, scaling)
        
        # 设置最小值
        if self.min_date:
            min_val = excel_date(self.min_date)
            self._set_scaling_value(scaling, 'min', str(min_val))
            print(f"  - 最小日期: {self.min_date.strftime('%Y-%m-%d')} (Excel: {min_val})")
        
        # 设置最大值
        if self.max_date:
            max_val = excel_date(self.max_date)
            self._set_scaling_value(scaling, 'max', str(max_val))
            print(f"  - 最大日期: {self.max_date.strftime('%Y-%m-%d')} (Excel: {max_val})")
    
    def _set_scaling_value(self, scaling_element, tag_name: str, value: str):
        """在 scaling 元素中设置 min/max"""
        full_tag = f"{{{NAMESPACES['c']}}}{tag_name}"
        
        existing = scaling_element.find(f'.//c:{tag_name}', namespaces=NAMESPACES)
        
        if existing is not None:
            existing.set('val', value)
        else:
            new_element = etree.Element(full_tag)
            new_element.set('val', value)
            scaling_element.append(new_element)
    
    def _set_date_format(self, axis_element, format_code: str):
        """
        设置日期轴的数字格式（numFmt）
        
        Args:
            axis_element: 轴元素（dateAx 或 catAx）
            format_code: Excel 格式代码（如 "yyyy/mm", "mm-dd" 等）
        """
        from lxml import etree
        
        # 查找或创建 numFmt 元素
        numfmt = axis_element.find('.//c:numFmt', namespaces=NAMESPACES)
        
        if numfmt is None:
            # 创建新的 numFmt 元素
            numfmt = etree.Element(f"{{{NAMESPACES['c']}}}numFmt")
            
            # ⭐ 关键：numFmt 应该在 tickLblPos 之后、crossAx 之前插入
            # 正确顺序：axId → scaling → delete → axPos → majorTickMark → minorTickMark → tickLblPos → numFmt → crossAx → crosses → auto → [时间单位]
            tick_lbl_pos_elements = axis_element.findall('.//c:tickLblPos', namespaces=NAMESPACES)
            if tick_lbl_pos_elements:
                tick_lbl_pos = tick_lbl_pos_elements[0]
                parent_list = list(axis_element)
                pos_index = parent_list.index(tick_lbl_pos)
                axis_element.insert(pos_index + 1, numfmt)
            else:
                # Fallback：在 crossAx 之前
                cross_ax_elements = axis_element.findall('.//c:crossAx', namespaces=NAMESPACES)
                if cross_ax_elements:
                    cross_ax = cross_ax_elements[0]
                    parent_list = list(axis_element)
                    cross_ax_index = parent_list.index(cross_ax)
                    axis_element.insert(cross_ax_index, numfmt)
                else:
                    # 最后的fallback：追加到最后
                    axis_element.append(numfmt)
        
        # 设置格式代码和 sourceLinked
        numfmt.set('formatCode', format_code)
        numfmt.set('sourceLinked', '0')  # 不链接到数据源
    
    def _set_date_range_from_data(self, date_ax, chart_element):
        """
        从数据中提取日期范围并设置到 dateAx 的 scaling 中
        
        这是关键修复！PowerPoint 的 dateAx 默认从 0（1900-01-01）开始计算。
        必须明确设置 min 和 max 值来指定实际的日期范围。
        
        Args:
            date_ax: dateAx 元素
            chart_element: 图表根元素
        """
        from lxml import etree
        
        # 查找第一个 cat 元素中的 numCache 或 numRef
        cat_elements = chart_element.findall('.//c:cat', namespaces=NAMESPACES)
        
        if not cat_elements:
            print("  ⚠️ 未找到分类数据，跳过日期范围设置")
            return
        
        cat_elem = cat_elements[0]
        
        # 尝试从 numCache 中提取日期值
        num_cache = cat_elem.find('.//c:numCache', namespaces=NAMESPACES)
        if num_cache is not None:
            pt_elements = num_cache.findall('.//c:pt', namespaces=NAMESPACES)
            if pt_elements:
                # 获取第一个和最后一个日期值
                first_pt = pt_elements[0]
                last_pt = pt_elements[-1]
                
                first_v = first_pt.find('c:v', namespaces=NAMESPACES)
                last_v = last_pt.find('c:v', namespaces=NAMESPACES)
                
                if first_v is not None and last_v is not None:
                    try:
                        min_val = float(first_v.text)
                        max_val = float(last_v.text)
                        
                        # 转换为日期显示
                        from datetime import datetime, timedelta
                        min_date = datetime(1899, 12, 30) + timedelta(days=min_val)
                        max_date = datetime(1899, 12, 30) + timedelta(days=max_val)
                        
                        # 设置 scaling 的 min 和 max
                        self._set_scaling_min_max(date_ax, min_val, max_val)
                        
                        print(f"  ✅ 已设置日期范围: {min_date.strftime('%Y-%m-%d')} ~ {max_date.strftime('%Y-%m-%d')}")
                        print(f"     (Excel 序列号: {min_val} ~ {max_val})")
                        return
                    except (ValueError, AttributeError) as e:
                        print(f"  ⚠️ 提取日期范围失败: {e}")
        
        print("  ⚠️ 未能从数据中提取日期范围")
    
    def _set_scaling_min_max(self, axis_element, min_val, max_val):
        """
        设置 scaling 元素的 min 和 max 值
        
        Args:
            axis_element: 轴元素（dateAx 或 catAx）
            min_val: 最小值（Excel 日期序列号）
            max_val: 最大值（Excel 日期序列号）
        """
        from lxml import etree
        
        # 查找或创建 scaling 元素
        scaling = axis_element.find('.//c:scaling', namespaces=NAMESPACES)
        
        if scaling is None:
            # 创建 scaling 元素
            scaling = etree.Element(f"{{{NAMESPACES['c']}}}scaling")
            
            # scaling 应该在 axId 之后、delete 之前插入
            ax_id_elements = axis_element.findall('.//c:axId', namespaces=NAMESPACES)
            if ax_id_elements:
                ax_id = ax_id_elements[0]
                parent_list = list(axis_element)
                ax_id_index = parent_list.index(ax_id)
                axis_element.insert(ax_id_index + 1, scaling)
            else:
                axis_element.insert(0, scaling)
        
        # 设置 min 值
        self._set_scaling_value(scaling, 'min', str(min_val))
        
        # 设置 max 值
        self._set_scaling_value(scaling, 'max', str(max_val))
    
    def _set_tick_label_position(self, axis_element, position: str):
        """
        设置刻度标签位置
        
        Args:
            axis_element: 轴元素（catAx 或 dateAx）
            position: 位置值（'low', 'high', 'nextTo'）
        """
        # 查找 tickLblPos 元素
        tick_lbl_pos = axis_element.find('.//c:tickLblPos', namespaces=NAMESPACES)
        
        if tick_lbl_pos is not None:
            # 更新现有元素
            tick_lbl_pos.set('val', position)
        else:
            # 如果不存在，不创建（使用默认值）
            pass
    
    def _set_label_interval(self, axis_element, interval: int):
        """
        设置标签显示间隔
        
        Args:
            axis_element: 轴元素（catAx 或 dateAx）
            interval: 间隔值（每隔多少个数据点显示一个标签）
        """
        from lxml import etree
        
        # 查找或创建 tickLblSkip 元素
        tick_lbl_skip = axis_element.find('.//c:tickLblSkip', namespaces=NAMESPACES)
        
        if tick_lbl_skip is None:
            # 创建新的 tickLblSkip 元素
            tick_lbl_skip = etree.Element(f"{{{NAMESPACES['c']}}}tickLblSkip")
            
            # tickLblSkip 应该在 auto 之后插入
            auto_elements = axis_element.findall('.//c:auto', namespaces=NAMESPACES)
            if auto_elements:
                auto_elem = auto_elements[0]
                parent_list = list(axis_element)
                auto_index = parent_list.index(auto_elem)
                axis_element.insert(auto_index + 1, tick_lbl_skip)
            else:
                # Fallback：追加到最后
                axis_element.append(tick_lbl_skip)
        
        # 设置间隔值
        tick_lbl_skip.set('val', str(interval))




# ============================================================================
# 便捷预设
# ============================================================================

# 每日刻度（适用于短期数据：1-30天）
DAILY_TICKS = DateAxisConfig(
    base_unit="days",
    major_unit=1.0,
    major_unit_scale="days",
    number_format="mm-dd",
)

# 每周刻度（适用于中期数据：1-6个月）
WEEKLY_TICKS = DateAxisConfig(
    base_unit="days",
    major_unit=7.0,
    major_unit_scale="days",
    number_format="yyyy-mm-dd",
)

# 每双周刻度（适用于季度数据）
BIWEEKLY_TICKS = DateAxisConfig(
    base_unit="days",
    major_unit=14.0,
    major_unit_scale="days",
    number_format="yyyy-mm-dd",
)

# 每月刻度（适用于年度数据）
MONTHLY_TICKS = DateAxisConfig(
    base_unit="months",
    major_unit=1.0,
    major_unit_scale="months",
    number_format="yyyy-mm",
)

# 每季度刻度（适用于多年数据）
QUARTERLY_TICKS = DateAxisConfig(
    base_unit="months",
    major_unit=3.0,
    major_unit_scale="months",
    number_format="yyyy-mm",
)

# 每年刻度（适用于长期数据）
YEARLY_TICKS = DateAxisConfig(
    base_unit="years",
    major_unit=1.0,
    major_unit_scale="years",
    number_format="yyyy",
)

