from logging import getLogger
from uuid import uuid4

import requests
from django.conf import settings

logger = getLogger(__file__)

api_key = settings.AI_SERVICE_API_KEY
base_url = settings.AI_SERVICE_DEMO_URL
headers = {"api-key": api_key}


class AIProcessing:
    """
    This class handles the AI processing for submissions

    For this to work, you must specify the AI_SERVICE_DEMO_URL
    and LEARNGUAL_AI_API_KEY in your settings.py file.
    """

    @staticmethod
    def analyze_audio(
        audio,
        reference_text,
        scripted: bool = False,
        prompt: str = None,
        language: str = None,
        correct_reference: bool = True,
        query_string: str = None,
    ):
        """
        this communicates with the AI model and returns the analysis results
        for the given audio
        """
        base_url = settings.AI_SERVICE_DEMO_URL
        if not str(base_url).endswith("/"):
            base_url += "/"

        uid = uuid4().hex
        files = {"audio_data": (f"{uid}-audio.mp3", audio)}
        payload = {
            "reference_text": reference_text,
            "scripted": scripted,
            "correct_reference": correct_reference,
        }

        if prompt:
            payload["prompt"] = prompt

        if language:
            payload["language"] = language

        if query_string:
            base_url += "?" + query_string

        response = requests.post(
            url=base_url, headers=headers, files=files, data=payload
        )
        if response.status_code == 200:
            return {"status": True, "response": response}

        else:
            return {"status": False, "response": response}

    @staticmethod
    def qa_generate(
        passage: str,
        questions: list[dict],
        question_set_id: str | None = None,
        test_id: str | None = None,
        context_label: str | None = None,
        query_string: str | None = None,
    ):
        """Generate canonical answers for a passage and questions.

        Mirrors the QA service `POST /qa/generate` endpoint.

        Args:
            passage: Content of the passage.
            questions: List of question dicts with keys: `id`, `prompt`.
            question_set_id: Optional question set id (required if `test_id` omitted).
            test_id: Optional test id (required if `question_set_id` omitted).
            context_label: Optional context label string (or null).
            query_string: Optional query string to append to URL (without leading `?`).

        Returns:
            dict: {"status": bool, "response": requests.Response}
        """
        base_url: str = settings.AI_SERVICE_DEMO_URL

        # Build versioned QA endpoint
        parts = str(base_url).split("/v1/")
        if len(parts) >= 2:
            qa_url = parts[0].rstrip("/") + "/v2/qa/generate"
        else:
            qa_url = base_url.removesuffix("/process").removesuffix("/process/")
            qa_url = qa_url.rstrip("/") + "/qa/generate"

        if query_string:
            qa_url += "?" + query_string
        payload = {
            "passage": passage,
            "questions": questions,
        }

        if question_set_id:
            payload["questionSetId"] = question_set_id
        if test_id:
            payload["testId"] = test_id
        if context_label is not None:
            payload["contextLabel"] = context_label
        try:
            response = requests.post(url=qa_url, headers=headers, json=payload)
        except requests.RequestException:
            return {"status": False, "response": {"message": "connection error"}}

        if response.status_code == 201:
            return {"status": True, "response": response}
        else:
            return {"status": False, "response": response}

    @staticmethod
    def qa_evaluate(
        answers: list[dict],
        passage_version: int | None = None,
        question_set_id: str | None = None,
        test_id: str | None = None,
        query_string: str | None = None,
    ):
        """Evaluate student answers against canonical answers.

        Mirrors the QA service `POST /qa/evaluate` endpoint.

        Args:
            answers: List of answer dicts with keys: `id`, and one of `response` or `audioUrl`.
            passage_version: Optional passage version to target (number).
            question_set_id: Optional question set id (required if `test_id` omitted).
            test_id: Optional test id (required if `question_set_id` omitted).
            query_string: Optional query string to append to URL (without leading `?`).

        Returns:
            dict: {"status": bool, "response": requests.Response}
        """
        base_url: str = settings.AI_SERVICE_DEMO_URL

        # Build versioned QA endpoint
        parts = str(base_url).split("/v1/")
        if len(parts) >= 2:
            qa_url = parts[0].rstrip("/") + "/v2/qa/evaluate"
        else:
            qa_url = base_url.removesuffix("/process").removesuffix("/process/")
            qa_url = qa_url.rstrip("/") + "/qa/evaluate"

        if query_string:
            qa_url += "?" + query_string

        payload = {
            "answers": answers,
        }

        if passage_version is not None:
            payload["passageVersion"] = passage_version
        if question_set_id:
            payload["questionSetId"] = question_set_id
        if test_id:
            payload["testId"] = test_id

        try:
            response = requests.post(url=qa_url, headers=headers, json=payload)
        except requests.RequestException:
            return {"status": False, "response": {"message": "connection error"}}

        if response.status_code == 201:
            return {"status": True, "response": response}
        else:
            return {"status": False, "response": response}

    @staticmethod
    def relevance(topic: str, essay: str, query_string: str = None):
        base_url = settings.AI_SERVICE_DEMO_URL

        if not str(base_url).endswith("/"):
            base_url += "/"

        base_url = base_url.rstrip("process/") + "/relevance/"

        if query_string:
            base_url += "?" + query_string

        response = requests.post(
            url=base_url,
            headers=headers,
            data={"topic": topic, "essay": essay},
        )

        if response.status_code == 200:
            return {"status": True, "response": response}

        else:
            return {"status": False, "response": response}

    def logic_evaluation(
        topic: str,
        essay: str,
        criteria: list[str] = None,
        query_string: str = None,
    ):
        """Evaluates the logical structure and reasoning of an essay in relation to a topic.

        Assesses the essay's logical flow, argument validity, and reasoning quality by sending
        it to an AI evaluation service. The evaluation can be customized with specific criteria.

        Args:
            topic: The topic or subject the essay should address.
            essay: The text content of the essay to be evaluated.
            criteria: List of evaluation criteria to assess. Defaults to:
                ["evidence", "coherence", "relevance", "critical_thinking"] if not provided.
                For pure logic evaluation, consider ["argument_structure", "fallacies", "reasoning"].

        Returns:
            dict: A dictionary containing:
                - status (bool): True if request was successful (HTTP 200), False otherwise
                - response (Union[requests.Response, dict]):
                    The full response object if successful, or error details if failed

        Raises:
            ConnectionError: If the request to the evaluation service fails.
            ValueError: If topic or essay is empty or None.
        """
        if not criteria:
            criteria = [
                "evidence",
                "coherence",
                "relevance",
                "critical_thinking",
            ]

        base_url = settings.AI_SERVICE_DEMO_URL

        if not str(base_url).endswith("/"):
            base_url += "/"

        base_url = base_url.removesuffix("/process/") + "/essay-evaluation/"

        if query_string:
            base_url += "?" + query_string

        try:
            response = requests.post(
                url=base_url,
                headers=headers,
                json={
                    "topic": topic,
                    "essay": essay,
                    "criteria": criteria,
                },
            )
        except ConnectionError:
            return {"status": False, "response": {"message": "connection error"}}
        if response.status_code == 200:
            return {"status": True, "response": response}
        else:
            return {"status": False, "response": response}

    def grammar(text_body: str, query_string: str = None):
        """
        Analyze and correct grammar in the provided text using AI service.

        Args:
            text_body (str): The text content to be analyzed for grammar corrections.
            query_string (str, optional): Additional query parameters to append to the request URL.

        Returns:
            dict: A dictionary containing the analysis status and response data.
                - status (bool): True if request was successful (status code 200), False otherwise.
                - response (requests.Response): The raw response object from the AI service.

                On successful response, the response.json() contains:
                - Original Speech (str): The original input text
                - Grammatical Correct Version (str): The corrected version of the text
                - Feedback (list): List of corrections made to the text
                - Grammar Score (int): Numerical score representing grammar quality (0-100)
                - Correction Operations (list, optional): Detailed correction operations with:
                    - operation (str): Type of correction (e.g., "substituted")
                    - original_word (str): The original word that was corrected
                    - replacement_word (str): The word that replaced the original
                    - position (int): Character position where the correction was made
                    - length (int): Length of the original word

        Example:
            >>> result = grammar("A boy fell on the log and broke his leg")
            >>> if result["status"]:
            ...     data = result["response"].json()
            ...     print(data["Grammatical Correct Version"])
            ...     # Output: "A boy fell on a log and broke his leg"
        """
        base_url: str = settings.AI_SERVICE_DEMO_URL

        if not str(base_url).endswith("/"):
            base_url += "/"

        base_url = base_url.removesuffix("/process/") + "/gammar/"
        if query_string:
            base_url += "?" + query_string

        response = requests.post(
            url=base_url,
            headers=headers,
            data={"speech": text_body},
        )

        if response.status_code == 200:
            return {"status": True, "response": response}

        else:
            return {"status": False, "response": response}

    def grammarv2(text_body: str, query_string: str = None):
        """
        Check grammar of provided text using AI service v2 grammar endpoint.
        This function sends a POST request to the AI service's v2 grammar check endpoint
        to analyze and correct grammatical errors in the provided text.
        Args:
            text_body (str): The text content to be checked for grammar errors.
            query_string (str, optional): Additional query parameters to append to the URL.
                                         Defaults to None.
        Returns:
            dict: A dictionary containing the grammar check results with the following structure:
                - status (bool): True if the request was successful (status code 200), False otherwise.
                - response (requests.Response): The raw response object from the API call.
                On successful response (status=True), the response.json() will contain:
                - originalText (str): The original input text
                - correctedText (str): The grammatically corrected version of the text
                - issues (list): List of grammar issues found, each containing:
                    - type (str): Type of grammar issue (e.g., "PrepositionUsage")
                    - message (str): Detailed explanation of the issue
                    - suggestion (str): Suggested correction
                    - severity (str): Severity level of the issue
                    - targetWord (str): The problematic word or phrase
                - metadata (dict): Processing information including:
                    - processingTime (int): Time taken to process in seconds
                    - agentsUsed (list): List of grammar agents used for analysis
                    - totalIssuesFound (int): Total number of issues detected
                    - overallScore (int): Overall grammar score
                - operations (list): Detailed edit operations showing text changes
        Raises:
            requests.RequestException: If there's an issue with the HTTP request.
        Note:
            Requires the 'settings.AI_SERVICE_DEMO_URL' to be properly configured
            and 'headers' to be defined in the module scope.
        """

        base_url: str = settings.AI_SERVICE_DEMO_URL

        urls = str(base_url).split("/v1/")
        if len(urls) < 1:
            return

        base_url = urls[0] + "/v2/" + "grammar/check"

        if query_string:
            base_url += "?" + query_string

        response = requests.post(
            url=base_url,
            headers=headers,
            data={"speech": text_body, "content": text_body},
        )

        if response.status_code == 200:
            return {"status": True, "response": response}

        else:
            return {"status": False, "response": response}

    def check_audio(audio, language="en"):
        """
        Analyze audio using the AI service.

        Args:
            audio: Audio file content (bytes or file-like object) to be analyzed.
            language (str, optional): Language code of the audio (default: "en").

        Returns:
            dict: {
                "status": True/False,
                "response": requests.Response or error details
            }

        Raises:
            requests.RequestException: If the HTTP request fails.

        Note:
            The 'audio' parameter should be the actual audio data, not a file path.
            The AI service endpoint must be properly configured in settings.

        Example:
            >>> with open("sample.mp3", "rb") as f:
            ...     result = AIProcessing.check_audio(f.read(), language="en")
            ...     if result["status"]:
            ...         print(result["response"].json())
        """
        base_url: str = settings.AI_SERVICE_DEMO_URL

        urls = str(base_url).split("/v1/")
        if len(urls) < 1:
            return {"status": False, "response": {"error": "Invalid base URL"}}

        base_url = urls[0] + "/v2/" + "grammar/check-audio"
        payload = {"language": language}

        uid = uuid4().hex
        files = [("file", (f"{uid}-audio.mp3", audio, "application/octet-stream"))]

        try:
            response = requests.request(
                "POST", base_url, headers=headers, data=payload, files=files
            )
            if response.status_code == 200:
                return {"status": True, "response": response}
            else:
                logger.error(
                    f"AI audio grammar check failed: {response.status_code} {response.text}"
                )
                return {"status": False, "response": response}
        except requests.RequestException as e:
            logger.error(f"RequestException during AI audio grammar check: {e}")
            return {"status": False, "response": {"error": str(e)}}

    @staticmethod
    def i18n_translate(language: str, items: list[dict], query_string: str = None):
        """
        Translate items with variable substitution using i18n endpoint.

        This endpoint supports translating text with placeholder variables that
        can be substituted after translation.

        Args:
            language: Target language code (e.g., "fr", "es", "de").
            items: List of translation items. Each item is a dict containing:
                - text (str): The text to translate
                - vars (dict, optional): Variables to substitute in the translated text
            query_string: Optional query parameters to append to the URL.

        Returns:
            dict: {
                "status": True/False,
                "response": requests.Response object
            }

        Example:
            >>> items = [
            ...     {"text": "Hello {name}!", "vars": {"name": "John"}},
            ...     {"text": "Goodbye!"}
            ... ]
            >>> result = AIProcessing.i18n_translate("fr", items)
            >>> if result["status"]:
            ...     print(result["response"].json())
        """
        base_url: str = settings.AI_SERVICE_DEMO_URL

        # Build i18n translate endpoint
        urls = str(base_url).split("/v1/")
        if len(urls) >= 2:
            i18n_url = urls[0].rstrip("/") + "/v1/i18n/translate"
        else:
            i18n_url = base_url.removesuffix("/process").removesuffix("/process/")
            i18n_url = i18n_url.rstrip("/") + "/i18n/translate"

        if not i18n_url.endswith("/"):
            i18n_url += "/"

        if query_string:
            i18n_url += "?" + query_string

        payload = {
            "language": language,
            "items": items,
        }

        try:
            response = requests.post(url=i18n_url, headers=headers, json=payload)
        except requests.RequestException as e:
            logger.error(f"RequestException during i18n translate: {e}")
            return {"status": False, "response": {"error": str(e)}}

        if response.status_code == 200:
            return {"status": True, "response": response}
        else:
            logger.error(
                f"i18n translate failed: {response.status_code} {response.text}"
            )
            return {"status": False, "response": response}

    @staticmethod
    def batch_translate(
        texts: list[str],
        source_language: str,
        target_language: str,
        query_string: str = None,
    ):
        """
        Batch translate multiple texts from source to target language.

        This endpoint efficiently translates multiple text strings in a single
        request, optimized for large-scale translation tasks.

        Args:
            texts: List of text strings to translate.
            source_language: Source language code (e.g., "en", "fr", "es").
            target_language: Target language code (e.g., "en", "fr", "es").
            query_string: Optional query parameters to append to the URL.

        Returns:
            dict: {
                "status": True/False,
                "response": requests.Response object
            }

        Example:
            >>> texts = [
            ...     "Hello, how are you?",
            ...     "I'm doing great!",
            ...     "See you later!"
            ... ]
            >>> result = AIProcessing.batch_translate(texts, "en", "fr")
            >>> if result["status"]:
            ...     translations = result["response"].json()
            ...     print(translations)
        """
        base_url: str = settings.AI_SERVICE_DEMO_URL

        # Build batch translation endpoint
        urls = str(base_url).split("/v1/")
        if len(urls) >= 2:
            translation_url = urls[0].rstrip("/") + "/v1/m/translation"
        else:
            translation_url = base_url.removesuffix("/process").removesuffix(
                "/process/"
            )
            translation_url = translation_url.rstrip("/") + "/m/translation"

        if not translation_url.endswith("/"):
            translation_url += "/"

        if query_string:
            translation_url += "?" + query_string

        payload = {
            "texts": texts,
            "source_language": source_language,
            "target_language": target_language,
        }

        try:
            response = requests.post(url=translation_url, headers=headers, json=payload)
        except requests.RequestException as e:
            logger.error(f"RequestException during batch translate: {e}")
            return {"status": False, "response": {"error": str(e)}}

        if response.status_code == 200:
            return {"status": True, "response": response}
        else:
            logger.error(
                f"Batch translate failed: {response.status_code} {response.text}"
            )
            return {"status": False, "response": response}
