"""Tests for ResumeService class."""

import pytest
from pathlib import Path
from unittest.mock import MagicMock, patch
from cveasy.services.resume_service import ResumeService
from cveasy.exceptions import NotFoundError, ResumeGenerationError
from cveasy.models.job import Job
from cveasy.models.bio import Bio
from cveasy.models.skill import Skill
from cveasy.models.experience import Experience
from cveasy.models.story import Story
from cveasy.models.link import Link
from cveasy.models.project import Project
from cveasy.models.education import Education


@pytest.fixture
def mock_storage():
    """Create a mock MarkdownStorage."""
    storage = MagicMock()
    return storage


@pytest.fixture
def mock_generator():
    """Create a mock ResumeGenerator."""
    generator = MagicMock()
    generator.generate_general_resume.return_value = "# Generated Resume\n\nContent"
    generator.generate_customized_resume.return_value = "# Customized Resume\n\nContent"
    generator.update_resume_from_check_report.return_value = "# Updated Resume\n\nContent"
    return generator


@pytest.fixture
def resume_service(temp_dir, mock_storage, mock_generator):
    """Create a ResumeService instance with mocked dependencies."""
    with patch("cveasy.services.resume_service.MarkdownStorage", return_value=mock_storage):
        with patch("cveasy.services.resume_service.ResumeGenerator", return_value=mock_generator):
            service = ResumeService(temp_dir)
            service.storage = mock_storage
            service.generator = mock_generator
            return service


def test_resume_service_init(temp_dir):
    """Test ResumeService initialization."""
    with patch("cveasy.services.resume_service.MarkdownStorage") as mock_storage_class:
        with patch("cveasy.services.resume_service.ResumeGenerator") as mock_generator_class:
            service = ResumeService(temp_dir)

            mock_storage_class.assert_called_once_with(temp_dir)
            mock_generator_class.assert_called_once()


def test_generate_general_resume_success(resume_service, mock_storage, mock_generator):
    """Test successful general resume generation."""
    # Setup mocks
    mock_storage.load_bio.return_value = Bio(name="John Doe", location="San Francisco, CA")
    mock_storage.list_skills.return_value = [
        Skill(name="Python", category="Programming", years=5, proficiency="Expert", related_experience=[], content="")
    ]
    mock_storage.list_experiences.return_value = []
    mock_storage.list_stories.return_value = []
    mock_storage.list_links.return_value = []
    mock_storage.list_projects.return_value = []
    mock_storage.list_educations.return_value = []
    mock_storage.save_resume.return_value = Path("/tmp/resume.md")

    result = resume_service.generate_general_resume()

    assert result == Path("/tmp/resume.md")
    mock_storage.load_bio.assert_called_once()
    mock_storage.list_skills.assert_called_once()
    mock_storage.list_experiences.assert_called_once()
    mock_storage.list_stories.assert_called_once()
    mock_storage.list_links.assert_called_once()
    mock_storage.list_projects.assert_called_once()
    mock_storage.list_educations.assert_called_once()
    mock_generator.generate_general_resume.assert_called_once()
    mock_storage.save_resume.assert_called_once_with("# Generated Resume\n\nContent")


def test_generate_general_resume_with_empty_data(resume_service, mock_storage, mock_generator):
    """Test general resume generation with empty data."""
    mock_storage.load_bio.return_value = None
    mock_storage.list_skills.return_value = []
    mock_storage.list_experiences.return_value = []
    mock_storage.list_stories.return_value = []
    mock_storage.list_links.return_value = []
    mock_storage.list_projects.return_value = []
    mock_storage.list_educations.return_value = []
    mock_storage.save_resume.return_value = Path("/tmp/resume.md")

    result = resume_service.generate_general_resume()

    assert result == Path("/tmp/resume.md")
    mock_generator.generate_general_resume.assert_called_once_with(
        skills=[],
        experiences=[],
        stories=[],
        links=[],
        projects=[],
        educations=[],
        bio=None,
    )


def test_generate_general_resume_storage_error(resume_service, mock_storage, mock_generator):
    """Test general resume generation when storage fails."""
    mock_storage.load_bio.side_effect = Exception("Storage error")

    with pytest.raises(ResumeGenerationError) as exc_info:
        resume_service.generate_general_resume()

    assert "Failed to generate general resume" in str(exc_info.value)
    assert "Storage error" in str(exc_info.value)


def test_generate_general_resume_generator_error(resume_service, mock_storage, mock_generator):
    """Test general resume generation when generator fails."""
    mock_storage.load_bio.return_value = None
    mock_storage.list_skills.return_value = []
    mock_storage.list_experiences.return_value = []
    mock_storage.list_stories.return_value = []
    mock_storage.list_links.return_value = []
    mock_storage.list_projects.return_value = []
    mock_storage.list_educations.return_value = []
    mock_generator.generate_general_resume.side_effect = Exception("Generator error")

    with pytest.raises(ResumeGenerationError) as exc_info:
        resume_service.generate_general_resume()

    assert "Failed to generate general resume" in str(exc_info.value)
    assert "Generator error" in str(exc_info.value)


def test_generate_customized_resume_success(resume_service, mock_storage, mock_generator):
    """Test successful customized resume generation."""
    application_id = "test-app-20240101"
    job = Job(
        name="Software Engineer",
        title="Senior Software Engineer",
        location="Remote",
        requirements="Python, AWS",
        pay="$150k-200k",
        content="Job description",
    )

    mock_storage.load_job.return_value = job
    mock_storage.load_bio.return_value = Bio(name="John Doe", location="San Francisco, CA")
    mock_storage.list_skills.return_value = []
    mock_storage.list_experiences.return_value = []
    mock_storage.list_stories.return_value = []
    mock_storage.list_links.return_value = []
    mock_storage.list_projects.return_value = []
    mock_storage.list_educations.return_value = []
    mock_storage.save_resume.return_value = Path("/tmp/custom-resume.md")

    result = resume_service.generate_customized_resume(application_id)

    assert result == Path("/tmp/custom-resume.md")
    mock_storage.load_job.assert_called_once_with(application_id)
    # Verify generator was called once with correct arguments
    assert mock_generator.generate_customized_resume.call_count == 1
    call_args = mock_generator.generate_customized_resume.call_args
    assert call_args.kwargs["job"] == job
    assert call_args.kwargs["skills"] == []
    assert call_args.kwargs["experiences"] == []
    assert call_args.kwargs["stories"] == []
    assert call_args.kwargs["links"] == []
    assert call_args.kwargs["projects"] == []
    assert call_args.kwargs["educations"] == []
    # Verify bio fields (timestamps are auto-generated, so check fields separately)
    assert call_args.kwargs["bio"].name == "John Doe"
    assert call_args.kwargs["bio"].location == "San Francisco, CA"
    mock_storage.save_resume.assert_called_once_with(
        "# Customized Resume\n\nContent",
        application_id=application_id
    )


def test_generate_customized_resume_job_not_found(resume_service, mock_storage):
    """Test customized resume generation when job not found."""
    application_id = "nonexistent-app"
    mock_storage.load_job.return_value = None

    with pytest.raises(NotFoundError) as exc_info:
        resume_service.generate_customized_resume(application_id)

    assert f"Job application '{application_id}' not found" in str(exc_info.value)
    mock_storage.load_job.assert_called_once_with(application_id)


def test_generate_customized_resume_generation_error(resume_service, mock_storage, mock_generator):
    """Test customized resume generation when generation fails."""
    application_id = "test-app-20240101"
    job = Job(
        name="Software Engineer",
        title="Senior Software Engineer",
        location="Remote",
        requirements="Python, AWS",
        pay="$150k-200k",
        content="Job description",
    )

    mock_storage.load_job.return_value = job
    mock_storage.load_bio.return_value = None
    mock_storage.list_skills.return_value = []
    mock_storage.list_experiences.return_value = []
    mock_storage.list_stories.return_value = []
    mock_storage.list_links.return_value = []
    mock_storage.list_projects.return_value = []
    mock_storage.list_educations.return_value = []
    mock_generator.generate_customized_resume.side_effect = Exception("Generation failed")

    with pytest.raises(ResumeGenerationError) as exc_info:
        resume_service.generate_customized_resume(application_id)

    assert f"Failed to generate customized resume for application '{application_id}'" in str(exc_info.value)
    assert "Generation failed" in str(exc_info.value)


def test_update_resume_from_check_report_success(resume_service, mock_storage, mock_generator):
    """Test successful resume update from check report."""
    application_id = "test-app-20240101"
    job = Job(
        name="Software Engineer",
        title="Senior Software Engineer",
        location="Remote",
        requirements="Python, AWS",
        pay="$150k-200k",
        content="Job description",
    )
    current_resume = "# Current Resume\n\nContent"
    check_report = "# Check Report\n\nSuggestions here"

    mock_storage.load_job.return_value = job
    mock_storage.load_resume.return_value = current_resume
    mock_storage.load_check_report.return_value = check_report
    mock_storage.load_bio.return_value = None
    mock_storage.list_skills.return_value = []
    mock_storage.list_experiences.return_value = []
    mock_storage.list_stories.return_value = []
    mock_storage.list_links.return_value = []
    mock_storage.list_projects.return_value = []
    mock_storage.list_educations.return_value = []
    mock_storage.save_resume.return_value = Path("/tmp/updated-resume.md")

    result = resume_service.update_resume_from_check_report(application_id)

    assert result == Path("/tmp/updated-resume.md")
    mock_storage.load_job.assert_called_once_with(application_id)
    mock_storage.load_resume.assert_called_once_with(application_id=application_id)
    mock_storage.load_check_report.assert_called_once_with(application_id)
    mock_generator.update_resume_from_check_report.assert_called_once_with(
        current_resume=current_resume,
        check_report=check_report,
        job=job,
        skills=[],
        experiences=[],
        stories=[],
        links=[],
        projects=[],
        educations=[],
        bio=None,
    )
    mock_storage.save_resume.assert_called_once_with(
        "# Updated Resume\n\nContent",
        application_id=application_id
    )


def test_update_resume_from_check_report_job_not_found(resume_service, mock_storage):
    """Test resume update when job not found."""
    application_id = "nonexistent-app"
    mock_storage.load_job.return_value = None

    with pytest.raises(NotFoundError) as exc_info:
        resume_service.update_resume_from_check_report(application_id)

    assert f"Job application '{application_id}' not found" in str(exc_info.value)
    mock_storage.load_job.assert_called_once_with(application_id)
    mock_storage.load_resume.assert_not_called()


def test_update_resume_from_check_report_resume_not_found(resume_service, mock_storage):
    """Test resume update when resume not found."""
    application_id = "test-app-20240101"
    job = Job(
        name="Software Engineer",
        title="Senior Software Engineer",
        location="Remote",
        requirements="Python, AWS",
        pay="$150k-200k",
        content="Job description",
    )

    mock_storage.load_job.return_value = job
    mock_storage.load_resume.return_value = None

    with pytest.raises(NotFoundError) as exc_info:
        resume_service.update_resume_from_check_report(application_id)

    assert f"No resume found for application '{application_id}'" in str(exc_info.value)
    mock_storage.load_job.assert_called_once_with(application_id)
    mock_storage.load_resume.assert_called_once_with(application_id=application_id)
    mock_storage.load_check_report.assert_not_called()


def test_update_resume_from_check_report_check_report_not_found(resume_service, mock_storage):
    """Test resume update when check report not found."""
    application_id = "test-app-20240101"
    job = Job(
        name="Software Engineer",
        title="Senior Software Engineer",
        location="Remote",
        requirements="Python, AWS",
        pay="$150k-200k",
        content="Job description",
    )
    current_resume = "# Current Resume\n\nContent"

    mock_storage.load_job.return_value = job
    mock_storage.load_resume.return_value = current_resume
    mock_storage.load_check_report.return_value = None

    with pytest.raises(NotFoundError) as exc_info:
        resume_service.update_resume_from_check_report(application_id)

    assert f"No check report found for application '{application_id}'" in str(exc_info.value)
    mock_storage.load_job.assert_called_once_with(application_id)
    mock_storage.load_resume.assert_called_once_with(application_id=application_id)
    mock_storage.load_check_report.assert_called_once_with(application_id)


def test_update_resume_from_check_report_generation_error(resume_service, mock_storage, mock_generator):
    """Test resume update when generation fails."""
    application_id = "test-app-20240101"
    job = Job(
        name="Software Engineer",
        title="Senior Software Engineer",
        location="Remote",
        requirements="Python, AWS",
        pay="$150k-200k",
        content="Job description",
    )
    current_resume = "# Current Resume\n\nContent"
    check_report = "# Check Report\n\nSuggestions"

    mock_storage.load_job.return_value = job
    mock_storage.load_resume.return_value = current_resume
    mock_storage.load_check_report.return_value = check_report
    mock_storage.load_bio.return_value = None
    mock_storage.list_skills.return_value = []
    mock_storage.list_experiences.return_value = []
    mock_storage.list_stories.return_value = []
    mock_storage.list_links.return_value = []
    mock_storage.list_projects.return_value = []
    mock_storage.list_educations.return_value = []
    mock_generator.update_resume_from_check_report.side_effect = Exception("Update failed")

    with pytest.raises(ResumeGenerationError) as exc_info:
        resume_service.update_resume_from_check_report(application_id)

    assert f"Failed to update resume for application '{application_id}'" in str(exc_info.value)
    assert "Update failed" in str(exc_info.value)


def test_update_resume_from_check_report_preserves_not_found_error(resume_service, mock_storage):
    """Test that NotFoundError is preserved and not wrapped."""
    application_id = "test-app-20240101"
    job = Job(
        name="Software Engineer",
        title="Senior Software Engineer",
        location="Remote",
        requirements="Python, AWS",
        pay="$150k-200k",
        content="Job description",
    )
    current_resume = "# Current Resume\n\nContent"
    check_report = "# Check Report\n\nSuggestions"

    mock_storage.load_job.return_value = job
    mock_storage.load_resume.return_value = current_resume
    mock_storage.load_check_report.return_value = check_report
    mock_storage.load_bio.side_effect = NotFoundError("Bio not found")

    with pytest.raises(NotFoundError) as exc_info:
        resume_service.update_resume_from_check_report(application_id)

    assert "Bio not found" in str(exc_info.value)


def test_get_resume_with_application_id(resume_service, mock_storage):
    """Test getting resume with application ID."""
    application_id = "test-app-20240101"
    resume_content = "# Resume\n\nContent"
    mock_storage.load_resume.return_value = resume_content

    result = resume_service.get_resume(application_id=application_id)

    assert result == resume_content
    mock_storage.load_resume.assert_called_once_with(application_id=application_id, date=None)


def test_get_resume_with_date(resume_service, mock_storage):
    """Test getting resume with date."""
    date = "20240101"
    resume_content = "# Resume\n\nContent"
    mock_storage.load_resume.return_value = resume_content

    result = resume_service.get_resume(date=date)

    assert result == resume_content
    mock_storage.load_resume.assert_called_once_with(application_id=None, date=date)


def test_get_resume_not_found(resume_service, mock_storage):
    """Test getting resume when not found."""
    mock_storage.load_resume.return_value = None

    result = resume_service.get_resume()

    assert result is None
    mock_storage.load_resume.assert_called_once_with(application_id=None, date=None)


def test_get_resume_with_both_params(resume_service, mock_storage):
    """Test getting resume with both application_id and date."""
    application_id = "test-app-20240101"
    date = "20240101"
    resume_content = "# Resume\n\nContent"
    mock_storage.load_resume.return_value = resume_content

    result = resume_service.get_resume(application_id=application_id, date=date)

    assert result == resume_content
    mock_storage.load_resume.assert_called_once_with(application_id=application_id, date=date)
