"""Routes for fetching example sentences from Tatoeba and Leipzig."""

import json
from datetime import datetime

from flask import Blueprint, Response, current_app, g, render_template

from words_to_readlang.services.fetcher import ExampleFetcher

from ..database import db
from ..models import Entry, LeipzigCache, TatoebaCache, Upload
from ..services import ValidationService

tatoeba_bp = Blueprint("tatoeba", __name__)


def _lookup_cache(word: str, src_lang: str, target_lang: str):
    """Check both caches for a previously fetched example.

    Returns a dict with keys matching what the fetch result provides,
    or None if not cached.
    """
    tc = TatoebaCache.query.filter_by(
        word=word, source_language=src_lang, target_language=target_lang
    ).first()
    if tc:
        return {
            "example": tc.sentence_text,
            "sentence_id": tc.sentence_id,
            "license": tc.license,
            "author": tc.author,
            "source": "tatoeba",
        }

    lc = LeipzigCache.query.filter_by(
        word=word, source_language=src_lang
    ).first()
    if lc:
        return {
            "example": lc.sentence_text,
            "sentence_id": lc.sentence_id,
            "license": "CC BY",
            "author": None,
            "source": "leipzig",
        }

    return None


def _save_cache(word: str, src_lang: str, target_lang: str,
                example: str, sentence_id, license_info: str,
                author, example_source: str, source_url: str = ""):
    """Store a fetched example in the appropriate cache table."""
    if example_source == "tatoeba":
        db.session.add(TatoebaCache(
            word=word,
            source_language=src_lang,
            target_language=target_lang,
            sentence_id=int(sentence_id),
            sentence_text=example,
            license=license_info,
            author=author or "",
            created_at=datetime.utcnow(),
        ))
    else:
        db.session.add(LeipzigCache(
            word=word,
            source_language=src_lang,
            sentence_id=str(sentence_id),
            sentence_text=example,
            source_url=source_url,
            created_at=datetime.utcnow(),
        ))


def _apply_to_entry(entry, example, sentence_id, license_info, author, example_source):
    """Write fetched example fields onto an Entry."""
    entry.edited_example = example
    entry.tatoeba_attempted = True
    entry.tatoeba_result = example
    entry.tatoeba_sentence_id = int(sentence_id) if example_source == "tatoeba" else None
    entry.example_license = license_info
    entry.example_author = author
    entry.example_source = example_source


@tatoeba_bp.route("/fetch/<int:entry_id>", methods=["POST"])
def fetch_example(entry_id):
    """HTMX endpoint to fetch an example for a single entry.

    Args:
        entry_id: Entry ID to fetch example for

    Returns:
        Rendered example HTML or error message
    """
    entry = Entry.query.get_or_404(entry_id)

    if entry.upload.session_id != g.session.id:
        return "Unauthorized", 403

    word = entry.current_word.split(" / ")[0].strip()
    src_lang = entry.upload.source_language
    target_lang = entry.upload.target_language

    cached = _lookup_cache(word, src_lang, target_lang)
    if cached:
        example = cached["example"]
        sentence_id = cached["sentence_id"]
        license_info = cached["license"]
        author = cached["author"]
        example_source = cached["source"]
    else:
        client = ExampleFetcher(delay=0.5, verbose=False)
        try:
            result = client.fetch_example_with_metadata(
                word, src_lang=src_lang, target_lang=target_lang
            )
            if result:
                example, sentence_id, license_info, author, example_source = result
                _save_cache(word, src_lang, target_lang,
                            example, sentence_id, license_info, author, example_source)
            else:
                entry.tatoeba_attempted = True
                db.session.commit()
                return '<span class="error">No example found</span>'
        except Exception as e:
            return f'<span class="error">Error: {str(e)}</span>'

    _apply_to_entry(entry, example, sentence_id, license_info, author, example_source)
    ValidationService.update_entry_validation(entry)
    db.session.commit()

    return render_template(
        "htmx/tatoeba_result.html",
        example=example,
        license=license_info,
        author=author,
        example_source=example_source,
        sentence_id=sentence_id,
    )


def _make_fetch_generator(entry_ids, src_lang, target_lang, prefer, app):
    """Return an SSE generator that fetches missing examples for the given entry IDs."""

    def generate():
        with app.app_context():
            total = len(entry_ids)

            if total == 0:
                yield f"data: {json.dumps({'done': True, 'message': 'No entries need examples'})}\n\n"
                return

            client = ExampleFetcher(delay=0.5, verbose=False)

            for idx, entry_id in enumerate(entry_ids, 1):
                entry = Entry.query.get(entry_id)
                if not entry:
                    continue

                word = entry.current_word.split(" / ")[0].strip()

                try:
                    cached = _lookup_cache(word, src_lang, target_lang)
                    if cached:
                        example = cached["example"]
                        sentence_id = cached["sentence_id"]
                        license_info = cached["license"]
                        author = cached["author"]
                        example_source = cached["source"]
                    else:
                        result = client.fetch_example_with_metadata(
                            word,
                            src_lang=src_lang,
                            target_lang=target_lang,
                            prefer=prefer,
                        )
                        if result:
                            example, sentence_id, license_info, author, example_source = result
                            _save_cache(word, src_lang, target_lang,
                                        example, sentence_id, license_info,
                                        author, example_source)
                        else:
                            entry.tatoeba_attempted = True
                            db.session.commit()
                            yield f"data: {json.dumps({'current': idx, 'total': total, 'entry_id': entry.id, 'success': False, 'word': word})}\n\n"
                            continue

                    _apply_to_entry(entry, example, sentence_id, license_info, author, example_source)
                    ValidationService.update_entry_validation(entry)
                    db.session.commit()

                    yield f"data: {json.dumps({'current': idx, 'total': total, 'entry_id': entry.id, 'success': True, 'word': word, 'source': example_source})}\n\n"

                except Exception as e:
                    yield f"data: {json.dumps({'current': idx, 'total': total, 'entry_id': entry.id, 'success': False, 'word': word, 'error': str(e)})}\n\n"

            yield f"data: {json.dumps({'done': True})}\n\n"

    return generate


@tatoeba_bp.route("/fetch-all/<int:upload_id>")
def fetch_all_missing(upload_id):
    """SSE endpoint to fetch all missing examples for an upload.

    Query parameters:
        prefer: "tatoeba" (default) or "leipzig" — which source to try first.

    Args:
        upload_id: Upload ID to process

    Returns:
        Server-Sent Events stream with progress updates
    """
    from flask import request as flask_request

    upload = Upload.query.get_or_404(upload_id)
    if upload.session_id != g.session.id:
        return "Unauthorized", 403

    prefer = flask_request.args.get("prefer", "tatoeba")
    if prefer not in ("tatoeba", "leipzig"):
        prefer = "tatoeba"

    entry_ids = [
        e.id for e in
        Entry.query.filter_by(upload_id=upload_id)
        .filter(Entry.validation_status.in_(["invalid_no_example", "invalid_word_missing"]))
        .all()
    ]

    app = current_app._get_current_object()
    generate = _make_fetch_generator(
        entry_ids, upload.source_language, upload.target_language, prefer, app
    )
    return Response(generate(), mimetype="text/event-stream")


@tatoeba_bp.route("/fetch-pool")
def fetch_pool_missing():
    """SSE endpoint to fetch all missing examples for a language pair in the pool.

    Query parameters:
        source: Source language code
        target: Target language code
        prefer: "tatoeba" (default) or "leipzig"

    Returns:
        Server-Sent Events stream with progress updates
    """
    from flask import request as flask_request

    source_lang = flask_request.args.get("source")
    target_lang = flask_request.args.get("target")
    prefer = flask_request.args.get("prefer", "tatoeba")
    if prefer not in ("tatoeba", "leipzig"):
        prefer = "tatoeba"

    if not source_lang or not target_lang:
        return "Missing source/target parameters", 400

    entry_ids = [
        e.id for e in
        Entry.query.join(Upload)
        .filter(
            Upload.session_id == g.session.id,
            Upload.source_language == source_lang,
            Upload.target_language == target_lang,
        )
        .filter(Entry.validation_status.in_(["invalid_no_example", "invalid_word_missing"]))
        .all()
    ]

    app = current_app._get_current_object()
    generate = _make_fetch_generator(
        entry_ids, source_lang, target_lang, prefer, app
    )
    return Response(generate(), mimetype="text/event-stream")
