from typing import Final

from Levenshtein import ratio

from template_project.application.common.data_structure import to_data_structure
from template_project.application.common.interactor import to_interactor
from template_project.application.common.unit_of_work import UnitOfWork
from template_project.application.resume.data_gateway import ResumeDataGateway
from template_project.application.resume.entity import Resume, ResumeEmbedding, ResumeId
from template_project.application.resume.resume_prediction_generator import ResumePredictionGenerator
from template_project.application.resume.vector_generator import ResumeEmbeddingVectorGenerator
from template_project.application.vacancy.data_gateway import VacancyDataGateway
from template_project.application.vacancy.data_structure import SuitableVacancy

EMBEDDING_TEXT_TEMPLATE: Final = """
    Позиция: {position}
    Опыт: {experience_type}
    Ключевые навыки: {key_skills}
    Описание: {about_me}
"""


def _calculate_skills_matching(resume_skills: list[str], vacancy_skills: list[str]) -> float:
    count_skills = 0
    ratio_skill_sum = 0.0
    for resume_key_skill in resume_skills:
        for vacancy_key_skill in vacancy_skills:
            ratio_skill = ratio(resume_key_skill, vacancy_key_skill)
            if ratio_skill != 0:
                count_skills += 1
                ratio_skill_sum += ratio_skill

    try:
        return ratio_skill_sum / count_skills
    except ZeroDivisionError:
        return 0.0


def _filter_and_sort_vacancies(
    resume: Resume,
    suitable_vacancies: list[SuitableVacancy],
    limit: int = 50,
) -> list[SuitableVacancy]:
    def calculate_priority(vacancy: SuitableVacancy) -> float:
        priority = vacancy.resume_similarity

        if resume.experience_type == vacancy.vacancy.experience_type:
            priority += 0.1

        if resume.key_skills:
            skills_matching = _calculate_skills_matching(resume.key_skills, vacancy.vacancy.key_skills)
            priority += skills_matching * 0.2

        return priority

    sorted_vacancies = sorted(suitable_vacancies, key=calculate_priority, reverse=True)

    return sorted_vacancies[:limit]


@to_data_structure
class PredictResumeRequest:
    resume_id: ResumeId


@to_interactor
class ResumePredictionInteractor:
    unit_of_work: UnitOfWork
    resume_data_gateway: ResumeDataGateway
    vacancy_data_gateway: VacancyDataGateway
    vector_generator: ResumeEmbeddingVectorGenerator
    resume_prediction_generator: ResumePredictionGenerator

    async def execute(self, request: PredictResumeRequest) -> None:
        resume = await self.resume_data_gateway.load_by_resume_id(request.resume_id)
        if resume is None:
            return

        embedding_text = EMBEDDING_TEXT_TEMPLATE.format_map({
            "position": resume.position,
            "experience_type": resume.experience_type.value,
            "key_skills": ", ".join(resume.key_skills),
            "about_me": resume.about_me,
        })
        vector = await self.vector_generator.generate(embedding_text)
        resume_embedding = ResumeEmbedding.factory(
            resume_id=resume.id,
            vector=vector,
        )

        suitable_vacancies_list = list(await self.vacancy_data_gateway.get_suitable(resume_embedding.vector))
        suitable_vacancies_filtered = _filter_and_sort_vacancies(resume, suitable_vacancies_list, limit=50)

        resume_prediction = await self.resume_prediction_generator.generate(
            resume=resume,
            suitable_vacancies=suitable_vacancies_filtered,
        )

        await self.unit_of_work.add(resume_embedding, resume_prediction)
        await self.unit_of_work.commit()
