import unittest
from unittest.mock import patch
from src import conductor_tech_lead
import pytest

class TestConductorTechLead(unittest.TestCase):
 def test_generate_tickets_retry_failure(self) -> None:
  with patch('src.ai_client.send') as mock_send:
   mock_send.return_value = "invalid json"
   # conductor_tech_lead.generate_tickets now raises RuntimeError on error after 3 attempts
   with pytest.raises(RuntimeError):
    conductor_tech_lead.generate_tickets("brief", "skeletons")
   assert mock_send.call_count == 3

 def test_generate_tickets_retry_success(self) -> None:
  with patch('src.ai_client.send') as mock_send:
   mock_send.side_effect = ["invalid json", '[{"Task": "Test"}]']
   tickets = conductor_tech_lead.generate_tickets("brief", "skeletons")
   assert tickets == [{"Task": "Test"}]
   assert mock_send.call_count == 2

 def test_generate_tickets_success(self) -> None:
  with patch('src.ai_client.send') as mock_send:
   mock_send.return_value = '[{"id": "T1", "description": "desc", "depends_on": []}]'
   tickets = conductor_tech_lead.generate_tickets("brief", "skeletons")
   self.assertEqual(len(tickets), 1)
   self.assertEqual(tickets[0]['id'], "T1")

class TestTopologicalSort(unittest.TestCase):
 def test_topological_sort_linear(self) -> None:
  tickets = [
   {"id": "t2", "depends_on": ["t1"]},
   {"id": "t1", "depends_on": []},
  ]
  sorted_tickets = conductor_tech_lead.topological_sort(tickets)
  self.assertEqual(sorted_tickets[0]['id'], "t1")
  self.assertEqual(sorted_tickets[1]['id'], "t2")

 def test_topological_sort_complex(self) -> None:
  tickets = [
   {"id": "t3", "depends_on": ["t1", "t2"]},
   {"id": "t1", "depends_on": []},
   {"id": "t2", "depends_on": ["t1"]},
  ]
  sorted_tickets = conductor_tech_lead.topological_sort(tickets)
  self.assertEqual(sorted_tickets[0]['id'], "t1")
  self.assertEqual(sorted_tickets[1]['id'], "t2")
  self.assertEqual(sorted_tickets[2]['id'], "t3")

 def test_topological_sort_cycle(self) -> None:
  tickets = [
   {"id": "t1", "depends_on": ["t2"]},
   {"id": "t2", "depends_on": ["t1"]},
  ]
  with self.assertRaises(ValueError) as cm:
   conductor_tech_lead.topological_sort(tickets)
  # Match against our new standard ValueError message
  self.assertIn("Dependency cycle detected", str(cm.exception))

 def test_topological_sort_empty(self) -> None:
  self.assertEqual(conductor_tech_lead.topological_sort([]), [])

 def test_topological_sort_missing_dependency(self) -> None:
  # If a ticket depends on something not in the list, we should handle it or let it fail.
  # The TrackDAG silently ignores missing dependencies, causing cycle detection to trigger.
  tickets = [
   {"id": "t1", "depends_on": ["missing"]},
  ]
  # Currently this raises ValueError due to cycle detection on incomplete sort
  with self.assertRaises(ValueError):
   conductor_tech_lead.topological_sort(tickets)

def test_topological_sort_vlog(vlogger) -> None:
 tickets = [
  {"id": "t2", "depends_on": ["t1"]},
  {"id": "t1", "depends_on": []},
 ]
 vlogger.log_state("Input Order", ["t2", "t1"], ["t2", "t1"])
 sorted_tickets = conductor_tech_lead.topological_sort(tickets)
 result_ids = [t['id'] for t in sorted_tickets]
 vlogger.log_state("Sorted Order", "N/A", result_ids)
 assert result_ids == ["t1", "t2"]
 vlogger.finalize("Topological Sort Verification", "PASS", "Linear dependencies correctly ordered.")
