
import json
import copy
from typing import List, Dict, Any, Optional, Tuple, Union
from datetime import datetime


class ReportGenerator:
    def __init__(self, template_json: Optional[Dict[str, Any]] = None):
        """
        Initialize the ReportGenerator with optional template JSON content.
        :param template_json: Optional initial JSON content
        """
        self.template_json = template_json

    def load_template(self, template_content: Dict[str, Any]) -> None:
        """
        Load the template JSON content.
        :param template_content: Template JSON content
        """
        self.template_json = template_content

    def save_template(self, output_path: str) -> None:
        """
        Save the current template JSON to a file.
        :param output_path: Path to save the JSON file
        """
        if self.template_json is None:
            raise ValueError("No template loaded to save")
        with open(output_path, "w", encoding="utf-8") as f:
            json.dump(self.template_json, f, indent=2, ensure_ascii=False)

    def locate_last_text_blocks(self, template_json: List[Dict[str, Any]]) -> Tuple[int, int]:
        """
        Locate the last text blocks that need modification (turbine ID and test date).
        Returns: (turbine_text_index, date_text_index)
        """
        turbine_index = -1
        date_index = -1

        # Reverse traversal to find the last matching text blocks
        for i in range(len(template_json)-1, -1, -1):
            item = template_json[i]
            if item.get("type") != "text":
                continue

            runs = item["content"].get("runs", [])
            if not runs:
                continue

            # Locate the last turbine ID text block (contains "#")
            if turbine_index == -1 and any("#" in run.get("text", "") for run in runs):
                turbine_index = i

            # Locate the last test date text block (contains "检测日期")
            if date_index == -1 and any("检测日期：2025年" in run.get("text", "") for run in runs):
                date_index = i

            # Early exit if both are found
            if turbine_index != -1 and date_index != -1:
                break

        if turbine_index == -1 or date_index == -1:
            raise ValueError("Unable to locate turbine ID or test date text blocks")
        return turbine_index, date_index

    def update_text_content(self, turbine_id: str, test_date: str) -> List[Dict[str, Any]]:
        """
        Update text content in the template (turbine ID and test date).
        Always modifies the last matching text block.
        """
        if self.template_json is None:
            raise ValueError("No template loaded")

        # Parse date
        year, month, day = test_date.split("-")

        # Deep copy original data
        updated_json = copy.deepcopy(self.template_json)

        # Locate the last text block positions
        turbine_index, date_index = self.locate_last_text_blocks(updated_json)

        # Update the last turbine ID text
        if turbine_index != -1:
            item = updated_json[turbine_index]
            runs = item["content"].get("runs", [])
            for run in runs:
                text = run.get("text", "")
                if "#" in text:
                    # Extract original turbine ID format (like #19), preserving the # symbol
                    original_id = next((t for t in text.split() if t.startswith("#")), "")
                    if original_id:
                        run["text"] = text.replace(original_id, turbine_id)

        # Update the last test date text
        if date_index != -1:
            item = updated_json[date_index]
            runs = item["content"].get("runs", [])

            # Find the date part in the run text
            for i, run in enumerate(runs):
                text = run.get("text", "")

                # Update year part
                if "2025年" in text:
                    run["text"] = text.replace("2025", year)
                # Update month number part
                elif text.isdigit() and len(text) == 2 and i > 0 and runs[i-1].get("text", "").endswith("年"):
                    run["text"] = month
                # Update day number part
                elif text.isdigit() and len(text) == 2 and i > 0 and runs[i-1].get("text", "").endswith("月"):
                    run["text"] = day

        self.template_json = updated_json
        return updated_json

    def generate_table_rows(self, image_data: List[Tuple[str, str]], template_row: List[Dict[str, Any]]) -> List[List[Dict[str, Any]]]:
        """
        Generate table rows dynamically based on image paths and resistance values.
        :param image_data: List of tuples containing image paths and resistance values [(image_path, resistance), ...]
        :param template_row: Template row (first row of data)
        :return: List containing all rows
        """
        rows = []
        for idx, (image_path, resistance) in enumerate(image_data, start=1):
            new_row = []
            for cell in template_row:
                # Deep copy cell data
                new_cell = copy.deepcopy(cell)
                # Update row index
                new_cell["row"] = idx
                # If it's the sequence number column, update text
                if new_cell["col"] == 0:
                    new_cell["content"][0]["runs"][0]["text"] = str(idx)
                # If it's the test result column (6th column), update resistance value
                elif new_cell["col"] == 6:
                    new_cell["content"][0]["runs"][0]["text"] = resistance
                # If it's the image column (7th column), clear content
                elif new_cell["col"] == 7:
                    new_cell["content"][0]["runs"][0]["text"] = ""
                new_row.append(new_cell)
            rows.append(new_row)
        return rows

    def generate_images(self, image_data: List[Tuple[str, str]]) -> List[Dict[str, Any]]:
        """
        Generate image information list.
        :param image_data: List of tuples containing image paths and resistance values [(image_path, resistance), ...]
        :return: List of image information
        """
        images = []
        for idx, (image_path, _) in enumerate(image_data, start=1):
            images.append({
                "image_id": idx,
                "file_name": image_path.split("\\")[-1],  # Extract filename
                "file_path": image_path,
                "size": 210043,  # Assume fixed size (should be calculated from file in reality)
                "width_inches": 1.1840277777777777,
                "height_inches": 2.0965277777777778,
                "location": {
                    "type": "table",
                    "table_index": 0,
                    "row": idx,
                    "cell": 7,
                    "para_index": 0,
                    "run_index": 0
                }
            })
        return images

    def process_dynamic_table(self, image_data: List[Tuple[str, str]]) -> List[Dict[str, Any]]:
        """
        Process JSON data containing multiple tables:
        1. Always process the last table (whether original or previously appended template)
        2. Add rows dynamically
        3. Save the currently processed table and the three text blocks above it as a new template
        """
        if self.template_json is None:
            raise ValueError("No template loaded")

        # Deep copy original data
        output_json = copy.deepcopy(self.template_json)

        # Find all table indices (including previously appended template tables)
        table_indices = [
            i for i, item in enumerate(output_json)
            if item.get("type") == "table"
        ]

        if not table_indices:
            raise ValueError("No tables found in JSON data")

        # Always process the last table (whether it's a template or not)
        last_table_idx = table_indices[-1]
        last_table = output_json[last_table_idx]

        # Save the currently processed table and the three text blocks above it as a new template
        saved_section = []
        text_blocks = []

        # Reverse search for three text blocks
        for i in range(last_table_idx - 1, -1, -1):
            if output_json[i].get("type") == "text":
                text_blocks.insert(0, i)
                if len(text_blocks) == 3:
                    break

        # Determine save range
        start_idx = text_blocks[0] if len(text_blocks) == 3 else max(0, last_table_idx - 2)
        saved_section = copy.deepcopy(output_json[start_idx:last_table_idx + 1])

        # Process table - add rows dynamically
        if len(last_table["content"]["cells"]) < 2:
            raise ValueError("Target table must contain at least two rows (header row and data row)")

        template_row = last_table["content"]["cells"][1]
        new_rows = self.generate_table_rows(image_data, template_row)
        new_images = self.generate_images(image_data)

        # Update table
        table_content = last_table["content"]
        table_content["rows"] = 1 + len(new_rows)
        table_content["cells"] = [table_content["cells"][0]] + new_rows
        table_content["row_heights"] = [table_content["row_heights"][0]] + [
            0.39791666666666664 for _ in new_rows
        ]
        table_content["images"] = new_images

        # Append new template to the end of JSON
        output_json.extend(saved_section)

        self.template_json = output_json
        return output_json

    def modify_weather_data(self, weather: str, temperature: str, humidity: str,
                            start_date: Union[datetime, str], end_date: Union[datetime, str]) -> List[Dict[str, Any]]:
        """
        Modify weather-related data in the first table of the JSON.
        :param weather: Weather condition (e.g., "多云")
        :param temperature: Temperature range (e.g., "22-28℃")
        :param humidity: Humidity range (e.g., "65-70")
        :param start_date: Start date (datetime object or string in YYYY-MM-DD format)
        :param end_date: End date (datetime object or string in YYYY-MM-DD format)
        :return: Modified JSON data
        """
        if self.template_json is None:
            raise ValueError("No template loaded")

        # Convert string dates to datetime objects if needed
        if isinstance(start_date, str):
            start_date = datetime.strptime(start_date, "%Y-%m-%d")
        if isinstance(end_date, str):
            end_date = datetime.strptime(end_date, "%Y-%m-%d")

        # Find the first table element
        first_table = None
        for item in self.template_json:
            if item.get("type") == "table":
                first_table = item
                break

        if not first_table:
            return self.template_json

        cells = first_table["content"]["cells"]

        # Traverse each row of the table
        for row in cells:
            for cell in row:
                # Check if it's the primary cell of a merged cell
                if cell.get("is_merged", False) and cell.get("merge_info", {}).get("is_primary", False):
                    # Process test date row (10th row, index 9)
                    if cell["row"] == 9 and cell["col"] == 1:
                        # Format dates
                        start_str = start_date.strftime("%Y年%m月%d日")
                        end_str = end_date.strftime("%Y年%m月%d日")
                        date_text = f"{start_str}   至   {end_str}"

                        # Update content of all merged cells
                        merge_range = cell["merge_info"]["merge_range"]
                        start_row, end_row = int(merge_range.split('-')[0].split(',')[0]), int(merge_range.split('-')[1].split(',')[0])
                        start_col, end_col = int(merge_range.split('-')[0].split(',')[1]), int(merge_range.split('-')[1].split(',')[1])

                        for r in range(start_row, end_row + 1):
                            for c in range(start_col, end_col + 1):
                                if cells[r][c].get("content") and len(cells[r][c]["content"]) > 0:
                                    if cells[r][c]["content"][0].get("runs"):
                                        cells[r][c]["content"][0]["runs"] = [
                                            {
                                                "text": date_text,
                                                "font": {
                                                    "name": "仿宋",
                                                    "size": 10.5,
                                                    "bold": False,
                                                    "italic": None,
                                                    "underline": None,
                                                    "color": None
                                                },
                                                "style": "Default Paragraph Font",
                                                "has_page_break": False
                                            }
                                        ]

                # Process weather, temperature, humidity (11th row, index 10)
                if cell["row"] == 10:
                    # First check if content and runs exist
                    if cell.get("content") and len(cell["content"]) > 0:
                        if cell["content"][0].get("runs") and len(cell["content"][0]["runs"]) > 0:
                            cell_text = cell["content"][0]["runs"][0].get("text", "")

                            # Process weather
                            if cell_text == "天气" and cell["col"] == 1:
                                # Find weather value cell (next column)
                                if len(cells[10]) > 2 and cells[10][2].get("content") and cells[10][2]["content"][0].get("runs"):
                                    cells[10][2]["content"][0]["runs"] = [
                                        {
                                            "text": weather,
                                            "font": {
                                                "name": "仿宋",
                                                "size": 10.5,
                                                "bold": False,
                                                "italic": None,
                                                "underline": None,
                                                "color": None
                                            },
                                            "style": "Default Paragraph Font",
                                            "has_page_break": False
                                        }
                                    ]

                            # Process temperature
                            elif cell_text == "温度" and cell["col"] == 3:
                                # Find temperature value cell (5th column)
                                if len(cells[10]) > 4 and cells[10][5].get("content") and cells[10][5]["content"][0].get("runs"):
                                    cells[10][5]["content"][0]["runs"] = [
                                        {
                                            "text": temperature,
                                            "font": {
                                                "name": "仿宋",
                                                "size": 10.5,
                                                "bold": False,
                                                "italic": None,
                                                "underline": None,
                                                "color": None
                                            },
                                            "style": "Default Paragraph Font",
                                            "has_page_break": False
                                        }
                                    ]

                            # Process humidity
                            elif cell_text == "相对湿度" and cell["col"] == 6:
                                # Find humidity value cell (next column)
                                if len(cells[10]) > 6 and cells[10][7].get("content") and cells[10][7]["content"][0].get("runs"):
                                    cells[10][7]["content"][0]["runs"] = [
                                        {
                                            "text": f"{humidity}%RH",
                                            "font": {
                                                "name": "仿宋",
                                                "size": 10.5,
                                                "bold": False,
                                                "italic": None,
                                                "underline": None,
                                                "color": None
                                            },
                                            "style": "Default Paragraph Font",
                                            "has_page_break": False
                                        }
                                    ]

        return self.template_json

    def generate_report(self, config: Dict[str, Any]) -> None:
        """
        Generate the final report with all modifications.
        :param config: Dictionary containing all configuration parameters:
            {
                "template_content": Dict[str, Any],  # Input template JSON content
                "output_path": str,                 # Path to save output JSON
                "turbine_id": str,                 # e.g. "#26"
                "test_date": str,                   # e.g. "2025-08-19"
                "image_data": List[Tuple[str, str]], # e.g. [(r"path\to\image1.png", "0.0169"), ...]
                "weather": str,                    # e.g. "多云"
                "temperature": str,                 # e.g. "22-28℃"
                "humidity": str,                    # e.g. "65-70"
                "start_date": str,                  # e.g. "2023-06-01"
                "end_date": str                     # e.g. "2023-06-15"
            }
        """
        # Validate required parameters
        if "template_content" not in config:
            raise ValueError("template_content must be specified in config")
        if "output_path" not in config:
            raise ValueError("output_path must be specified in config")

        # Load template
        self.load_template(config["template_content"])

        # Update text content if provided
        if config.get("turbine_id") and config.get("test_date"):
            self.update_text_content(config["turbine_id"], config["test_date"])

        # Process dynamic table if image data provided
        if config.get("image_data"):
            self.process_dynamic_table(config["image_data"])

        # Modify weather data if provided
        if all(key in config for key in ["weather", "temperature", "humidity", "start_date", "end_date"]):
            self.modify_weather_data(
                weather=config["weather"],
                temperature=config["temperature"],
                humidity=config["humidity"],
                start_date=config["start_date"],
                end_date=config["end_date"]
            )

        # Save the result
        self.save_template(config["output_path"])
        print(f"Report successfully generated and saved to: {config['output_path']}")


# Example usage

def generate_report_from_java(json_config):
    try:
        # 解析JSON配置
        config = json.loads(json_config)

        # 检查template_content是否为空
        if not config.get('template_content'):
            if 'output_path' in config:
                try:
                    # 从output_path读取JSON内容
                    with open(config['output_path'], 'r', encoding='utf-8') as f:
                        config['template_content'] = json.load(f)

                except Exception as e:
                    raise ValueError(f"无法从output_path读取模板内容: {str(e)}")
            else:
                raise ValueError("template_content为空且未提供有效的output_path")

        # 如果template_content是字符串，尝试解析为JSON
        if isinstance(config['template_content'], str):
            try:
                config['template_content'] = json.loads(config['template_content'])
            except json.JSONDecodeError:
                # 如果不是有效的JSON字符串，保持原样
                pass

        # 生成报告
        generator = ReportGenerator()
        return generator.generate_report(config)

    except json.JSONDecodeError as e:
        raise ValueError(f"无效的JSON配置: {str(e)}")
    except KeyError as e:
        raise ValueError(f"缺少必要字段: {str(e)}")
    except Exception as e:
        raise RuntimeError(f"生成报告时出错: {str(e)}")