import json
import logging

_logger = logging.getLogger(__name__)

ACADEMIC_PASSAGE_WORD_RANGES = {
    1: (700, 900),
    2: (800, 1000),
    3: (900, 1100),
}

GENERAL_PASSAGE_WORD_RANGES = {
    1: (400, 700),
    2: (600, 900),
    3: (800, 1100),
}

LISTENING_SPEAKER_MAP = {
    1: 'conversation',
    2: 'monologue',
    3: 'conversation',
    4: 'monologue',
}

LISTENING_CONTEXT_MAP = {
    1: ['social', 'everyday'],
    2: ['social', 'everyday'],
    3: ['academic', 'training'],
    4: ['academic', 'lecture'],
}

AUTO_SCORED_TYPES = [
    'multiple_choice', 'true_false_not_given', 'yes_no_not_given',
    'matching_headings', 'matching_information', 'sentence_completion',
    'summary_completion', 'form_completion', 'map_labeling',
]


class IeltsValidator:
    """Two-layer IELTS content validator: format compliance and structural checks."""

    def validate_passage(self, passage_record):
        """Validate reading passage structure and constraints.

        Academic passages: section 1 (700-900w), section 2 (800-1000w), section 3 (900-1100w).
        General passages: section 1 (400-700w), section 2 (600-900w), section 3 (800-1100w).
        """
        errors = []

        section_num = passage_record.get('section_num', 0)
        if section_num < 1 or section_num > 3:
            errors.append(f'Invalid section_num {section_num}, must be 1-3')

        body_text = passage_record.get('body_text', '')
        word_count = len(body_text.split()) if body_text else 0

        exam_type = passage_record.get('exam_type', 'academic')
        word_ranges = ACADEMIC_PASSAGE_WORD_RANGES if exam_type == 'academic' else GENERAL_PASSAGE_WORD_RANGES
        expected = word_ranges.get(section_num, (400, 1100))

        if word_count < expected[0] or word_count > expected[1]:
            errors.append(
                f'Word count {word_count} outside expected range '
                f'{expected[0]}-{expected[1]} for {exam_type} section {section_num}'
            )

        if not body_text or not body_text.strip():
            errors.append('body_text must not be empty')

        if not passage_record.get('topic_category'):
            errors.append('topic_category must not be empty')

        if not passage_record.get('title'):
            errors.append('Passage title is required')

        questions = passage_record.get('questions', [])
        if questions:
            q_count = len(questions)
            if section_num == 1 and not (10 <= q_count <= 15):
                errors.append(f'Section 1 should have 10-15 questions, got {q_count}')
            elif section_num == 2 and not (12 <= q_count <= 15):
                errors.append(f'Section 2 should have 12-15 questions, got {q_count}')
            elif section_num == 3 and not (13 <= q_count <= 15):
                errors.append(f'Section 3 should have 13-15 questions, got {q_count}')

        return {'passed': len(errors) == 0, 'errors': errors}

    def validate_audio(self, audio_record):
        """Validate audio/listening content structure.

        Parts 1 & 3 must be conversation (2+ speakers).
        Parts 2 & 4 must be monologue (single speaker).
        """
        errors = []

        part = audio_record.get('part', 0)
        if part < 1 or part > 4:
            errors.append(f'Invalid part {part}, must be 1-4')

        expected_format = LISTENING_SPEAKER_MAP.get(part)
        actual_format = audio_record.get('speaker_format', '')
        if expected_format and actual_format and actual_format != expected_format:
            errors.append(
                f'Part {part} requires {expected_format} format, got {actual_format}'
            )

        context_type = audio_record.get('context_type', '')
        allowed = LISTENING_CONTEXT_MAP.get(part, [])
        if allowed and context_type and context_type not in allowed:
            errors.append(
                f'context_type "{context_type}" invalid for part {part}, '
                f'expected one of: {allowed}'
            )

        if not audio_record.get('audio_url'):
            errors.append('audio_url must not be empty')

        duration = audio_record.get('duration_seconds', 0)
        if duration > 0:
            if part in (1, 2) and duration > 600:
                errors.append(f'Part {part} audio should not exceed 10 minutes, got {duration}s')
            if part in (3, 4) and duration > 900:
                errors.append(f'Part {part} audio should not exceed 15 minutes, got {duration}s')

        if not audio_record.get('transcript'):
            errors.append('Transcript is recommended for listening content')

        questions = audio_record.get('questions', [])
        if questions:
            q_count = len(questions)
            if not (8 <= q_count <= 12):
                errors.append(f'Listening part should have 8-12 questions, got {q_count}')

        return {'passed': len(errors) == 0, 'errors': errors}

    def validate_writing_prompt(self, prompt_record):
        """Validate writing prompt requirements.

        Task 1: min_words >= 150, academic needs visual_url.
        Task 2: min_words >= 250.
        """
        errors = []

        task_type = prompt_record.get('task_type', '')
        min_words = prompt_record.get('min_words', 0)

        if task_type == 'task1':
            if min_words < 150:
                errors.append(f'Task 1 requires min_words >= 150, got {min_words}')
            exam_type = prompt_record.get('exam_type', '')
            if exam_type == 'academic' and not prompt_record.get('visual_url'):
                errors.append('Academic Task 1 requires a visual_url (chart/graph/diagram)')
            if not prompt_record.get('prompt_text'):
                errors.append('Task 1 prompt_text is required')
        elif task_type == 'task2':
            if min_words < 250:
                errors.append(f'Task 2 requires min_words >= 250, got {min_words}')
            if not prompt_record.get('prompt_text'):
                errors.append('Task 2 prompt_text (essay topic) is required')
        else:
            errors.append(f'Unknown task_type: {task_type}')

        if not prompt_record.get('rubric_id'):
            errors.append('rubric must be assigned')

        time_limit = prompt_record.get('time_limit_minutes', 0)
        if task_type == 'task1' and time_limit > 0 and time_limit != 20:
            errors.append(f'Task 1 recommended time is 20 minutes, got {time_limit}')
        if task_type == 'task2' and time_limit > 0 and time_limit != 40:
            errors.append(f'Task 2 recommended time is 40 minutes, got {time_limit}')

        return {'passed': len(errors) == 0, 'errors': errors}

    def validate_speaking_card(self, card_record):
        """Validate speaking card structure.

        Part 1: needs questions list (4-5 questions).
        Part 2: needs topic + bullet_points (3-4 points) + follow-up question.
        Part 3: needs questions list (4-6 discussion questions).
        """
        errors = []

        part = card_record.get('part', 0)
        if part < 1 or part > 3:
            errors.append(f'Invalid part {part}, must be 1-3')

        if part == 1:
            questions = card_record.get('questions', [])
            if not questions:
                errors.append('Part 1 requires a non-empty questions list')
            elif len(questions) < 4:
                errors.append(f'Part 1 should have at least 4 questions, got {len(questions)}')
            if not card_record.get('topic'):
                errors.append('Part 1 topic is required')

        if part == 2:
            bullet_points = card_record.get('bullet_points', [])
            if not bullet_points:
                errors.append('Part 2 requires non-empty bullet_points')
            elif len(bullet_points) < 3:
                errors.append(f'Part 2 should have at least 3 bullet points, got {len(bullet_points)}')
            if not card_record.get('topic'):
                errors.append('Part 2 topic card is required')
            if not card_record.get('follow_up'):
                errors.append('Part 2 should include a follow-up question')

        if part == 3:
            questions = card_record.get('questions', [])
            if not questions:
                errors.append('Part 3 requires a non-empty questions list')
            elif len(questions) < 4:
                errors.append(f'Part 3 should have at least 4 discussion questions, got {len(questions)}')

        return {'passed': len(errors) == 0, 'errors': errors}

    def validate_question(self, question_record):
        """Validate question record.

        Auto-scored types need correct_answer.
        IRT params: discrimination [0.2, 3.0], difficulty [-4, 4], guessing [0, 0.5].
        """
        errors = []

        q_type = question_record.get('question_type', '')
        if q_type in AUTO_SCORED_TYPES:
            if not question_record.get('correct_answer'):
                errors.append(f'correct_answer required for auto-scored type "{q_type}"')

        if q_type == 'multiple_choice':
            options = question_record.get('options', [])
            if options and len(options) < 3:
                errors.append('Multiple choice questions should have at least 3 options')
            correct = question_record.get('correct_answer')
            if correct and options and correct not in options:
                errors.append('correct_answer must be one of the provided options')

        discrimination = question_record.get('irt_discrimination', 1.0)
        if discrimination < 0.2 or discrimination > 3.0:
            errors.append(
                f'irt_discrimination {discrimination} out of recommended range [0.2, 3.0]'
            )

        difficulty = question_record.get('irt_difficulty', 0.0)
        if difficulty < -4 or difficulty > 4:
            errors.append(
                f'irt_difficulty {difficulty} out of valid range [-4, 4]'
            )

        guessing = question_record.get('irt_guessing', 0.25)
        if guessing < 0 or guessing > 0.5:
            errors.append(
                f'irt_guessing {guessing} out of valid range [0, 0.5]'
            )

        if not question_record.get('question_text'):
            errors.append('question_text must not be empty')

        return {'passed': len(errors) == 0, 'errors': errors}

    def run_full_validation(self, env, content_records):
        """Run all validators and create ielts.standards.check records.

        Args:
            env: Odoo environment.
            content_records: list of dicts with '_type' key indicating content type.

        Returns:
            dict summary with 'total', 'passed', 'failed', 'results', and 'all_passed'.
        """
        validators = {
            'passage': self.validate_passage,
            'audio': self.validate_audio,
            'writing_prompt': self.validate_writing_prompt,
            'speaking_card': self.validate_speaking_card,
            'question': self.validate_question,
        }

        results = []
        passed_count = 0
        failed_count = 0

        CheckModel = env['encoach.ielts.standards.check'].sudo()

        for record in content_records:
            content_type = record.get('_type', '')
            validator = validators.get(content_type)
            if not validator:
                results.append({
                    'content_type': content_type,
                    'passed': False,
                    'errors': [f'Unknown content type: {content_type}'],
                })
                failed_count += 1
                continue

            result = validator(record)
            results.append({
                'content_type': content_type,
                'passed': result['passed'],
                'errors': result.get('errors', []),
            })

            if result['passed']:
                passed_count += 1
            else:
                failed_count += 1

            content_id = record.get('id', 0)
            check_type_map = {
                'passage': 'reading_format',
                'audio': 'listening_format',
                'writing_prompt': 'writing_format',
                'speaking_card': 'speaking_format',
                'question': 'question_format',
            }

            try:
                CheckModel.create({
                    'content_id': content_id,
                    'content_type': content_type,
                    'check_type': check_type_map.get(content_type, 'format_compliance'),
                    'passed': result['passed'],
                    'error_detail': json.dumps(result.get('errors', [])),
                })
            except Exception as e:
                _logger.error("Failed to create standards check record: %s", e)

        return {
            'total': len(content_records),
            'passed': passed_count,
            'failed': failed_count,
            'all_passed': failed_count == 0,
            'results': results,
        }
