"""All broker responses here are mocked unit cases; no actual old credentials or broker calls."""
import gzip,hashlib,json,subprocess,sys,tempfile,unittest
from pathlib import Path
from unittest.mock import patch
from alpaca_rotation_check import AlpacaCredentialRotationCheck,credentials
from infinbot_common import Rejected
from run_agent_validation import import_public_sample
from datamine_adapter import parse_timesales
from tests.test_datamine import raw,row

ROOT=Path(__file__).resolve().parents[1]

class RotationTests(unittest.TestCase):
    def setUp(self):
        self.temp=tempfile.TemporaryDirectory();self.root=Path(self.temp.name)
        self.old=self.root/'old.json';self.new=self.root/'new.json'
        for f,key in ((self.old,'TESTOLDKEY123456'),(self.new,'TESTNEWKEY123456')):
            f.write_text(json.dumps({'mode':'paper','api_key':key,'api_secret':'TESTPRIVATEPAIR1234567890'}));f.chmod(0o600)
    def tearDown(self):self.temp.cleanup()
    def test_only_old_401_and_new_valid_account_allow_diagnostic_switch(self):
        observed=iter([{'http_status':401,'account_payload_valid':False},{'http_status':200,'account_payload_valid':True}])
        engine=AlpacaCredentialRotationCheck(transport=lambda *a:next(observed));r=engine.run(self.old,self.new,'paper')
        self.assertEqual(r['status'],'OLD_401_AND_NEW_AUTHENTICATION_OBSERVED')
        self.assertEqual(engine.current_headers['APCA-API-KEY-ID'],'TESTNEWKEY123456')
        self.assertFalse(r['production_configuration_changed']);self.assertFalse(r['global_revocation_verified'])
        self.assertNotIn('TESTPRIVATEPAIR',json.dumps(r));self.assertNotIn('TESTOLDKEY',json.dumps(r))
    def test_old_200_blocks_and_new_not_called(self):
        calls=[];engine=AlpacaCredentialRotationCheck(transport=lambda *a:calls.append(a) or {'http_status':200})
        self.assertEqual(engine.run(self.old,self.new,'paper')['status'],'OLD_CREDENTIALS_STILL_ACCEPTED')
        self.assertEqual(len(calls),1);self.assertIsNone(engine.current_headers)
    def test_unexpected_codes_never_count_as_401(self):
        for code in (403,404,429,500):
            engine=AlpacaCredentialRotationCheck(transport=lambda *a:{'http_status':code})
            self.assertEqual(engine.run(self.old,self.new,'paper')['status'],'OLD_CREDENTIAL_REJECTION_UNVERIFIED');self.assertIsNone(engine.current_headers)
    def test_network_exception_not_offline_pass(self):
        def unavailable(*a):raise TimeoutError('PRIVATE_ERROR_CONTENT_DO_NOT_DISPLAY')
        engine=AlpacaCredentialRotationCheck(transport=unavailable);r=engine.run(self.old,self.new,'paper')
        self.assertEqual(r['status'],'BROKER_ROTATION_CHECK_UNVERIFIED');self.assertNotIn('PRIVATE_ERROR_CONTENT',json.dumps(r))
    def test_same_pairs_fail_before_network(self):
        self.new.write_bytes(self.old.read_bytes())
        with patch('alpaca_rotation_check.account_status') as unused:
            calls=[];engine=AlpacaCredentialRotationCheck(transport=lambda *a:calls.append(a))
            self.assertEqual(engine.run(self.old,self.new,'paper')['status'],'BROKER_ROTATION_CHECK_UNVERIFIED');self.assertFalse(calls)
    def test_wrong_mode_never_gets_relabelled(self):
        with self.assertRaises(Rejected):credentials(self.old,'live')
    def test_hmac_raw_token_not_alpaca_pair(self):
        self.old.write_text('EXTERNAL-HMAC-TOKEN-ONLY-0123456789abcdef')
        with self.assertRaises(Exception):credentials(self.old,'paper')
    def test_mock_strings_rejected(self):
        self.old.write_text(json.dumps({'mode':'paper','api_key':'SAMPLE_EXPOSED_KEY','api_secret':'SAMPLE_EXPOSED_SECRET'}))
        with self.assertRaises(Rejected):credentials(self.old,'paper')
    def test_validation_cli_missing_evidence_is_nonzero(self):
        p=subprocess.run([sys.executable,str(ROOT/'run_agent_validation.py'),'--workspace',str(self.root),'--skip-sample'],capture_output=True,text=True)
        self.assertEqual(p.returncode,1);r=json.loads(p.stdout)
        self.assertEqual(r['status'],'VALIDATION_INCOMPLETE');self.assertFalse(r['production_configuration_changed'])
    def test_sample_import_exact_bytes_no_ledger_write(self):
        content=raw([list(__import__('datamine_adapter').FIELDS),row()]);f=self.root/'official-case.csv.gz';f.write_bytes(gzip.compress(content))
        with patch('run_agent_validation.SAMPLE_SHA256',hashlib.sha256(content).hexdigest()):
            # Unit profile uses explicit ES; construct an ES row without claiming it is the public provider file.
            es=row();es[3]='ES';es[4]='ESH5';content=raw([list(__import__('datamine_adapter').FIELDS),es]);f.write_bytes(gzip.compress(content))
            with patch('run_agent_validation.SAMPLE_SHA256',hashlib.sha256(content).hexdigest()):r=import_public_sample(f,self.root/'logs')
        self.assertFalse(r['ledger_modified']);self.assertFalse(r['position_allocation_verified'])
        self.assertTrue(Path(r['sample_path']).is_file());self.assertEqual(f.read_bytes(),Path(r['sample_path']).read_bytes())
    def test_calendar_empty_quantity_and_null_volume_sequence_are_explicit(self):
        spread=row();spread[4]='MESH5-MESM5';spread[13]='';spread[14]=''
        volume=row('2');volume[2]='';volume[10]='VOL';volume[11]=volume[12]=volume[13]=''
        r=parse_timesales(raw([spread,volume]),'MES')
        self.assertEqual(r['calendar_spread_rows'],1);self.assertEqual(r['non_execution_price_records'],1)
        self.assertEqual(r['unsequenced_volume_records'],1);self.assertFalse(r['position_allocation_verified'])

if __name__=='__main__':unittest.main()
