import json

from osa_tool.analytics.sourcerank import SourceRank
from osa_tool.config.settings import ConfigLoader
from osa_tool.models.models import ModelHandler, ModelHandlerFactory
from osa_tool.readmegen.context.article_content import PdfParser
from osa_tool.readmegen.context.article_path import get_pdf_path
from osa_tool.readmegen.context.files_contents import FileProcessor
from osa_tool.readmegen.postprocessor.response_cleaner import process_text
from osa_tool.readmegen.prompts.prompts_builder import PromptBuilder
from osa_tool.readmegen.utils import extract_example_paths, extract_relative_paths
from osa_tool.utils import logger


class LLMClient:
    def __init__(self, config_loader: ConfigLoader):
        self.config_loader = config_loader
        self.config = self.config_loader.config
        self.prompts = PromptBuilder(config_loader)
        self.model_handler: ModelHandler = ModelHandlerFactory.build(self.config)
        self.sourcerank = SourceRank(config_loader)
        self.tree = self.sourcerank.tree

    def get_responses(self) -> tuple[str, str, str]:
        """
        Extracts core features, overview, and optionally a Getting Started section from the repository.

        This method processes key files to extract core features and a general overview of the project.
        If example files are detected (e.g., scripts or notebooks demonstrating usage), it also generates
        a Getting Started section. Each part is generated by sending prompts to the model and cleaned before returning.

        Returns:
            tuple[str, str, str]: A tuple containing:
                - core_features: Main functionalities and capabilities of the project.
                - overview: General description and project context.
                - getting_started: (Optional) Setup or usage guide if examples are available.
        """
        logger.info("Started generating README-style summary.")
        key_files = self.get_key_files()
        key_files_content = FileProcessor(self.config_loader, key_files).process_files()

        logger.info("Generating core features of the project...")
        core_features = self.model_handler.send_request(self.prompts.get_prompt_core_features(key_files_content))

        logger.info("Generating project overview...")
        overview = self.model_handler.send_request(self.prompts.get_prompt_overview(core_features))

        core_features = process_text(core_features)
        overview = process_text(overview)

        logger.info("Attempting to generate Getting Started section...")
        examples_files = extract_example_paths(self.tree)
        examples_content = FileProcessor(self.config_loader, examples_files).process_files()
        getting_started = self.model_handler.send_request(self.prompts.get_prompt_getting_started(examples_content))
        getting_started = process_text(getting_started)

        logger.info("README-style summary generation completed.")
        return core_features, overview, getting_started

    def get_responses_article(self, article: str) -> tuple[str, str, str, str]:
        """
        Generates an article-style summary of the repository based on key files and associated PDF documentation.

        Args:
            article (str): Path to a research or descriptive PDF document associated with the project.

        Returns:
            tuple[str, str, str]: A tuple containing:
             - overview: General description and project context.
             - content: Content section based on key files and documentation.
             - algorithms: Description of algorithms used.
        """
        logger.info("Started generating Article-style summary.")
        key_files = self.get_key_files()
        key_files_content = FileProcessor(self.config_loader, key_files).process_files()

        logger.info("Generating summary of key files...")
        files_summary = self.model_handler.send_request(self.prompts.get_prompt_files_summary(key_files_content))

        path_to_pdf = get_pdf_path(article)
        pdf_content = PdfParser(path_to_pdf).data_extractor()

        logger.info("Generating summary of PDF content...")
        pdf_summary = self.model_handler.send_request(self.prompts.get_prompt_pdf_summary(pdf_content))

        logger.info("Generating project overview from combined sources...")
        overview = self.model_handler.send_request(self.prompts.get_prompt_overview_article(files_summary, pdf_summary))

        logger.info("Generating content section...")
        content = self.model_handler.send_request(self.prompts.get_prompt_content_article(files_summary, pdf_summary))

        logger.info("Generating algorithm description...")
        algorithms = self.model_handler.send_request(
            self.prompts.get_prompt_algorithms_article(key_files_content, pdf_summary)
        )

        logger.info("Attempting to generate Getting Started section...")
        examples_files = extract_example_paths(self.tree)
        examples_content = FileProcessor(self.config_loader, examples_files).process_files()
        getting_started = self.model_handler.send_request(self.prompts.get_prompt_getting_started(examples_content))

        overview = process_text(overview)
        content = process_text(content)
        algorithms = process_text(algorithms)
        getting_started = process_text(getting_started)

        logger.info("Article-style summary generation completed.")
        return overview, content, algorithms, getting_started

    def get_key_files(self) -> list:
        """Identifies key files from the project repository using model analysis."""
        key_files_response = self.model_handler.send_request(self.prompts.get_prompt_preanalysis())
        key_files_cleaned = process_text(key_files_response)

        try:
            response_json = json.loads(key_files_cleaned)
            key_files = response_json.get("key_files", [])
            if not isinstance(key_files, list):
                raise ValueError("Invalid format: 'key_files' must be a list.")

        except (json.JSONDecodeError, ValueError) as e:
            raise ValueError(f"Failed to parse JSON response: {e}") from e

        key_files = extract_relative_paths(key_files)
        return key_files

    def get_citation_from_readme(self) -> str:
        logger.info("Detecting citations in README...")
        response = self.model_handler.send_request(self.prompts.get_prompt_detect_citation())
        response = process_text(response)
        try:
            response_json = json.loads(response)
        except (json.JSONDecodeError, ValueError) as e:
            raise ValueError(f"Failed to parse JSON response: {e}") from e

        return response_json["citation"]

    def refine_readme(self, generated_readme: str) -> str:
        logger.info("Refining README files...")
        refine_step1 = process_text(
            self.model_handler.send_request(self.prompts.get_prompt_refine_readme_step1(generated_readme))
        )

        refine_step2 = process_text(
            self.model_handler.send_request(self.prompts.get_prompt_refine_readme_step2(refine_step1))
        )

        refine_step3 = process_text(
            self.model_handler.send_request(self.prompts.get_prompt_refine_readme_step3(refine_step2))
        )
        try:
            response_json = json.loads(refine_step3)
        except (json.JSONDecodeError, ValueError) as e:
            raise ValueError(f"Failed to parse JSON response: {e}") from e

        return response_json["readme"]

    def clean(self, readme: str) -> str:
        logger.info("Cleaning README...")
        clean_step1 = process_text(self.model_handler.send_request(self.prompts.get_prompt_clean_readme_step1(readme)))
        clean_step2 = process_text(
            self.model_handler.send_request(self.prompts.get_prompt_clean_readme_step2(clean_step1))
        )
        clean_step3 = process_text(
            self.model_handler.send_request(self.prompts.get_prompt_clean_readme_step3(clean_step2))
        )
        try:
            response_json = json.loads(clean_step3)
        except (json.JSONDecodeError, ValueError) as e:
            raise ValueError(f"Failed to parse JSON response: {e}") from e

        return response_json["readme"]

    def get_article_name(self, pdf_content: str) -> str:
        logger.info("Getting article name from pdf...")
        response = process_text(
            self.model_handler.send_request(self.prompts.get_prompt_article_name_extraction(pdf_content))
        )
        try:
            response_json = json.loads(response)
        except (json.JSONDecodeError, ValueError) as e:
            raise ValueError(f"Failed to parse JSON response: {e}") from e

        return response_json["article_name"]
