from typing import Any, Dict, List

from evalscope.api.benchmark import BenchmarkMeta, VisionLanguageAdapter
from evalscope.api.dataset import Sample
from evalscope.api.evaluator.state import TaskState
from evalscope.api.messages.chat_message import ChatMessageUser
from evalscope.api.messages.content import Content, ContentImage, ContentText
from evalscope.api.metric.scorer import Score
from evalscope.api.registry import register_benchmark
from evalscope.constants import Tags
from evalscope.utils.import_utils import check_import
from evalscope.utils.io_utils import bytes_to_base64
from evalscope.utils.logger import get_logger

logger = get_logger()

SUBSET_LIST = [
    'OE_MM_maths_en_COMP',
    'OE_MM_maths_zh_CEE',
    'OE_MM_maths_zh_COMP',
    'OE_MM_physics_en_COMP',
    'OE_MM_physics_zh_CEE',
    'OE_TO_maths_en_COMP',
    'OE_TO_maths_zh_CEE',
    'OE_TO_maths_zh_COMP',
    'OE_TO_physics_en_COMP',
    'OE_TO_physics_zh_CEE',
    'TP_MM_maths_en_COMP',
    'TP_MM_maths_zh_CEE',
    'TP_MM_maths_zh_COMP',
    'TP_MM_physics_en_COMP',
    'TP_TO_maths_en_COMP',
    'TP_TO_maths_zh_CEE',
    'TP_TO_maths_zh_COMP',
    'TP_TO_physics_en_COMP',
]


@register_benchmark(
    BenchmarkMeta(
        name='olympiad_bench',
        pretty_name='OlympiadBench',
        tags=[Tags.MATH, Tags.REASONING],
        description="""
## Overview

OlympiadBench is an Olympiad-level bilingual multimodal scientific benchmark featuring 8,476 problems from mathematics and physics competitions, including the Chinese college entrance exam (CEE). It provides rigorous evaluation of advanced scientific reasoning.

## Task Description

- **Task Type**: Olympiad-Level Math/Physics Problem Solving
- **Input**: Problem text with optional images (up to 9)
- **Output**: Mathematical answer or proof
- **Domains**: Mathematics, Physics (bilingual: English and Chinese)

## Key Features

- 8,476 Olympiad-level problems
- Bilingual support (English and Chinese)
- Covers both Mathematics and Physics
- Subset naming convention:
  - `OE`: Open-Ended problems
  - `TP`: Theorem Proving problems
  - `MM`: Multimodal (with images)
  - `TO`: Text-Only
  - `CEE`: Chinese Entrance Exam
  - `COMP`: Comprehensive competition problems

## Evaluation Notes

- Default evaluation uses the **train** split
- Primary metric: **Accuracy** with mathematical judging
- Answers should be in \\boxed{} format
- **Note**: `TP` (Theorem Proving) subsets cannot be auto-evaluated currently
- Supports numerical precision/error thresholds for approximate answers
""",
        dataset_id='AI-ModelScope/OlympiadBench',
        subset_list=SUBSET_LIST,
        metric_list=['acc'],
        eval_split='train',
        prompt_template='{question}\nPlease reason step by step, and put your final answer within \\boxed{{}}.',
    )
)
class OlympiadBenchAdapter(VisionLanguageAdapter):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        check_import(
            module_name=['latex2sympy2_extended'],
            extra='olympiad_bench',
            raise_error=True,
            feature_name=self.pretty_name
        )

    def record_to_sample(self, record: Dict[str, Any]) -> Sample:
        """Generate prompt for a single item."""
        from .utils import OlympiadBenchPrompter

        question = record.get('question', '')
        language = record.get('language', 'English')
        subject = record.get('subject', 'Math')
        question_type = record.get('question_type', '')
        answer_type = record.get('answer_type', '')
        is_multiple_answer = record.get('is_multiple_answer', False)
        unit = record.get('unit', '')
        # Generate prompt
        prompt = OlympiadBenchPrompter().make_prompt(
            problem=question,
            language=language,
            subject=subject,
            question_type=question_type,
            answer_type=answer_type,
            is_multiple_answer=is_multiple_answer,
            unit=unit,
        )
        # Construct content list
        content_list: List[Content] = []
        # Add images if available
        for i in range(9):
            image = record.get(f'image_{i+1}')
            if image:
                image_base64 = bytes_to_base64(image['bytes'], format='jpg', add_header=True)
                content_list.append(ContentImage(image=image_base64))
                prompt = prompt.replace(f'<image_{i+1}>', f'[image_{i+1}]')  # replace html tag
        # Add text content
        content_list.insert(0, ContentText(text=prompt))

        final_answer = record.get('final_answer', [])
        return Sample(
            input=[ChatMessageUser(content=content_list)],
            target=','.join(final_answer) if final_answer else '',
            metadata={
                'id': record.get('id', ''),
                'subfield': record.get('subfield', ''),
                'context': record.get('context', ''),
                'solution': record.get('solution', []),
                'final_answer': record.get('final_answer', []),
                'is_multiple_answer': is_multiple_answer,
                'unit': unit,
                'answer_type': answer_type,
                'question_type': question_type,
                'language': language,
                'subject': subject,
                'error': record.get('error', None),
            },
        )

    def extract_answer(self, prediction: str, task_state: TaskState):
        import re

        if task_state.metadata['language'] == 'Chinese':
            matches = re.findall('所以最终答案是(.*)', prediction)
        else:
            matches = re.findall('So the final answer is (.*)', prediction)

        # If found matches, take the last one, otherwise return the whole text
        if matches:
            return matches[-1].strip()
        return prediction

    def match_score(self, original_prediction, filtered_prediction, reference, task_state) -> Score:
        from .utils import MathJudger

        judger = MathJudger()
        score = Score(
            extracted_prediction=filtered_prediction,
            prediction=original_prediction,
        )
        question = task_state.metadata
        model_answer = filtered_prediction
        # Get precision/error threshold from reference if available
        answer_type = question['answer_type']
        try:
            if 'Tuple' in answer_type:  # 目前可机评的数据中 没有 need_human_evaluate
                judge_result = judger.judge(model_answer, question['final_answer'][0])
            else:
                if question['error']:
                    if ',' in question['error']:
                        precisions = question['error'].split(',')
                        precisions = [float(p) if p else 1e-8 for p in precisions]
                        judge_result = judger.judge(model_answer, question['final_answer'][0], precisions)
                    else:
                        precision = float(question['error'])
                        judge_result = judger.judge(model_answer, question['final_answer'][0], precision)
                else:
                    judge_result = judger.judge(model_answer, question['final_answer'][0])
        except Exception as e:
            logger.warning(f'Error in judging answer: {e}')
            judge_result = False

        score.value = {'acc': float(judge_result)}
        return score
