"""Tests for the autonomous bot loop with mocked sources, scorer, and orchestrator."""
from __future__ import annotations

import json
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, patch

from job_search_agent.bot import JobBot
from job_search_agent.models import (
    ApplicationLog,
    BotConfig,
    JobDescription,
    MatchScore,
    TailoredCoverLetter,
    TailoredResume,
)


def _make_bot(tmp_path, sample_candidate) -> JobBot:
    config = BotConfig(
        query="python",
        sources=["remoteok"],
        interval_minutes=0,
        min_score=50,
        max_jobs_per_scan=5,
        output_dir=str(tmp_path),
    )
    bot = JobBot(candidate=sample_candidate, config=config)
    bot._running = False  # Only run one cycle
    return bot


class TestDeduplication:
    def test_mark_applied_persists(self, tmp_path, sample_candidate):
        bot = _make_bot(tmp_path, sample_candidate)
        bot._mark_applied("https://example.com/job/1")
        bot._mark_applied("https://example.com/job/2")

        assert "https://example.com/job/1" in bot._applied_urls
        assert "https://example.com/job/2" in bot._applied_urls

        data = json.loads((tmp_path / "applied_jobs.json").read_text())
        assert len(data["applied_urls"]) == 2

    def test_load_applied_urls(self, tmp_path, sample_candidate):
        # Pre-seed the file
        (tmp_path / "applied_jobs.json").write_text(
            json.dumps({"applied_urls": ["https://old.com/job"]})
        )
        bot = _make_bot(tmp_path, sample_candidate)
        assert "https://old.com/job" in bot._applied_urls


class TestLogging:
    def test_write_log_appends(self, tmp_path, sample_candidate):
        bot = _make_bot(tmp_path, sample_candidate)
        bot._write_log(ApplicationLog(action="SCORED", job_title="Dev", score=90, status="OK"))
        bot._write_log(ApplicationLog(action="APPLIED", job_title="Dev", status="SUBMITTED"))

        lines = (tmp_path / "application_log.jsonl").read_text().strip().split("\n")
        assert len(lines) == 2
        assert "SCORED" in lines[0]
        assert "APPLIED" in lines[1]


class TestProcessJob:
    def test_skips_low_score(self, tmp_path, sample_candidate, sample_job):
        bot = _make_bot(tmp_path, sample_candidate)
        low_score = MatchScore(score=20, reasoning="No overlap", apply_recommendation=False)

        with patch.object(bot.scorer, "score", return_value=low_score):
            bot._process_job(sample_job)

        # Should have logged SCORED + SKIPPED
        lines = (tmp_path / "application_log.jsonl").read_text().strip().split("\n")
        actions = [json.loads(l)["action"] for l in lines]
        assert "SCORED" in actions
        assert "SKIPPED" in actions

    def test_proceeds_on_high_score(self, tmp_path, sample_candidate, sample_job):
        bot = _make_bot(tmp_path, sample_candidate)
        high_score = MatchScore(score=85, reasoning="Great fit", apply_recommendation=True)

        mock_result = {
            "job": sample_job,
            "tailored_resume": TailoredResume(
                summary="Expert", highlighted_skills=["Python"], bullet_points=["Led team"]
            ),
            "tailored_cover_letter": TailoredCoverLetter(
                intro="Dear...", body="I have...", closing="Best,"
            ),
            "portfolio_suggestions": [],
            "interview_session": MagicMock(),
        }

        with patch.object(bot.scorer, "score", return_value=high_score):
            with patch.object(bot.orchestrator, "full_pipeline_for_job", return_value=mock_result):
                bot._process_job(sample_job)

        lines = (tmp_path / "application_log.jsonl").read_text().strip().split("\n")
        actions = [json.loads(l)["action"] for l in lines]
        assert "SCORED" in actions
        assert "TAILORED" in actions
        assert "PACKAGED" in actions

        # Verify package was saved
        pkg_dirs = list((tmp_path / "packages").iterdir())
        assert len(pkg_dirs) == 1


class TestScanAllSources:
    def test_scans_configured_sources(self, tmp_path, sample_candidate, sample_job):
        bot = _make_bot(tmp_path, sample_candidate)

        mock_source = MagicMock()
        mock_source.return_value.search.return_value = [sample_job]

        with patch("job_search_agent.bot.RemoteOKSource", mock_source):
            jobs = bot._scan_all_sources()

        assert len(jobs) == 1
        assert jobs[0].title == "Senior Backend Engineer"

    def test_handles_source_error_gracefully(self, tmp_path, sample_candidate):
        bot = _make_bot(tmp_path, sample_candidate)

        mock_source = MagicMock()
        mock_source.return_value.search.side_effect = Exception("Network error")

        with patch("job_search_agent.bot.RemoteOKSource", mock_source):
            jobs = bot._scan_all_sources()

        assert jobs == []
