import fs from 'node:fs';import {expect,test}from '@playwright/test';import {loginWithCredentials}from '../../../../src/qa/auth';import {adminEnv,storeEnv,root,recovery,keys,preset,selected,openPreset,save,screen,fresh,go,settled,refresh,persist,record,blocked,type Role,type Check}from './permission-support';
process.env.PW_TEST_SCREENSHOT_NO_FONTS_READY='1';
for(const role of ['CO','BO','VE'] as Role[])test(`[${role}] 실제 전월 데이터·재조회·계정 전환`,async({browser},info)=>{
 test.setTimeout(20*60_000);test.skip(process.env.QA_ALLOW_PERMISSION_MUTATION!=='1','BLOCKED: mutation guard missing');
 const f=`${root}/matrix/${role}.json`;const rows:Check[]=JSON.parse(fs.readFileSync(f,'utf8'));const original=JSON.parse(fs.readFileSync(`${recovery}/${role}-baseline.json`,'utf8'));const c=await browser.newContext({storageState:adminEnv().storageState,viewport:{width:1600,height:1000}});const a=await c.newPage();let p=await fresh(browser,role);let offline=false;let cdp:any;
 const put=(id:string,ok:boolean,detail:string,evidence:string[])=>{for(let i=rows.length-1;i>=0;i--)if(rows[i].id===id&&rows[i].status==='BLOCKED')rows.splice(i,1);record(rows,role,'live data/session',id,ok,detail,evidence);persist(role,rows);};
 const toggle=async(st:boolean)=>{await openPreset(a,role);await save(a,role,{'스토어 거래조회':true,'스토어 정산조회':st},'전월 실제 데이터 정산 '+(st?'ON':'OFF'));await refresh(p);await settled(p);await screen(p,info,`${role}-data-${st?'on':'off'}-refresh`);await p.context().close();p=await fresh(browser,role);};
 try{
  await go(p,'/');let first:any;let firstTotal:any;let firstAmounts:string[]=[];
  for(const st of [true,false]){
   await toggle(st);await go(p,'/transactions');const requests:any[]=[];const listener=async(r:any)=>{if(new URL(r.url()).pathname==='/api/v1/store/transactions'&&r.request().method()==='GET'){const payload=(await r.json()).payload;requests.push({payload,url:r.url()});}};p.on('response',listener);
   await p.getByRole('button',{name:'전월',exact:true}).click();const rp=p.waitForResponse(r=>new URL(r.url()).pathname==='/api/v1/store/transactions/total');await p.getByRole('button',{name:'조회',exact:true}).click();const total=(await(await rp).json()).payload;await settled(p);
   const more=p.getByRole('button',{name:'더보기',exact:true});let pages=0;while(await more.count()&&pages<5){await more.click();await p.getByText('불러오는 중...',{exact:true}).waitFor({state:'hidden',timeout:60000});await settled(p);pages++;}
   const shot=await screen(p,info,`${role}-all-previous-month-${st?'settlement-on':'settlement-off'}`);const texts=await p.locator('article').last().locator('ul > li').allTextContents();const content=requests.flatMap(x=>x.payload?.items?.content??x.payload?.content??[]);const unique=[...new Map(content.map(x=>[x.transactionId,x])).values()] as any[];
   const money=(v:number)=>`${Math.trunc(v).toLocaleString('ko-KR')}원`;
   const availableOk=unique.length>0&&unique.every((x,i)=>{const text=texts[i]??'';const amount=x.afterAmount??x.settlementAmount;return text.includes(money(x.amount))&&(st?amount==null||text.includes(`(${money(amount)})`):!text.match(/\([^)]*원\)/));});
   if(role==='VE'&&unique.length){const owner=JSON.parse(fs.readFileSync(`${recovery}/VE-user-detail.json`,'utf8')).payload.departmentName;put('R04',unique.every(x=>x.vendorName===owner)&&await p.locator('select').count()===0,'실제 전월 모든 거래의 가맹점명이 로그인 소속과 일치하며 부서 필터 없음',[shot]);}
   if(unique.length){const bothStates=unique.some(x=>x.amount>0)&&unique.some(x=>x.amount<0||String(x.status).includes('CANCEL'));if(bothStates)put(st?'T04':'T05',availableOk,`${unique.length}개 실제 승인·취소 거래에서 거래금액 및 정산 ${st?'표시':'숨김'} 대조`,[shot]);else blocked(rows,role,st?'T04':'T05',`${unique.length}개 승인 거래 표시 대조=${availableOk}, 취소 거래는 없음`,[shot]);
    if(st){first=unique.map(x=>({id:x.transactionId,amount:x.amount,status:x.status}));firstTotal=total;firstAmounts=texts;const zero=unique.some(x=>(x.afterAmount??x.settlementAmount)===0);const negative=unique.some(x=>(x.afterAmount??x.settlementAmount)<0);if(zero&&negative&&bothStates)put('T09',availableOk,'실제 0원·음수 정산 및 승인·취소 금액·부호 대조',[shot]);else blocked(rows,role,'T09',`전월 실제 데이터: 0원 정산=${zero}, 음수 정산=${negative}, 승인·취소 모두=${bothStates}`,[shot]);}
    else put('T06',JSON.stringify(first)===JSON.stringify(unique.map(x=>({id:x.transactionId,amount:x.amount,status:x.status})))&&JSON.stringify(firstTotal)===JSON.stringify(total),'전월 전체 페이지의 거래 ID·거래금액·상태·합계가 정산 권한 ON/OFF에서 동일',[shot]);
    if(pages>0)put('T08',availableOk,`${pages}회 실제 더보기로 추가된 거래도 권한별 표시 일치`,[shot]);
   }else blocked(rows,role,'T09','전월 실제 거래 목록 0건',[shot]);p.off('response',listener);
   if(!st){
    cdp=await p.context().newCDPSession(p);await cdp.send('Network.enable');await cdp.send('Network.emulateNetworkConditions',{offline:true,latency:0,downloadThroughput:-1,uploadThroughput:-1});offline=true;await p.getByRole('button',{name:'조회',exact:true}).click();await p.waitForTimeout(1000);const error=await screen(p,info,`${role}-query-offline-error`);const errorText=await p.locator('body').innerText();await cdp.send('Network.emulateNetworkConditions',{offline:false,latency:0,downloadThroughput:-1,uploadThroughput:-1});offline=false;await p.getByRole('button',{name:'당일',exact:true}).click();await p.getByRole('button',{name:'조회',exact:true}).click();await settled(p);const retry=await screen(p,info,`${role}-query-online-retry`);const retryText=await p.locator('body').innerText();blocked(rows,role,'H08','거래 화면의 실제 네트워크 실패·복구 부분 확인. 홈 재조회 검증 후 최종 판정',[error,retry]);await cdp.detach();cdp=undefined;
    cdp=await p.context().newCDPSession(p);await cdp.send('Network.enable');await cdp.send('Network.setCacheDisabled',{cacheDisabled:true});await cdp.send('Network.setBlockedURLs',{urls:['*transactions/trend*']});let homeRequestFailed=false;const failed=(r:any)=>{if(r.url().includes('/transactions/trend'))homeRequestFailed=true;};p.on('requestfailed',failed);await go(p,'/');const homeError=await screen(p,info,`${role}-home-trend-network-failure`);await cdp.send('Network.setBlockedURLs',{urls:[]});await refresh(p);await settled(p);const homeRetry=await screen(p,info,`${role}-home-trend-live-retry`);put('H08',homeRequestFailed&&await p.getByText('금일매출',{exact:true}).count()>0&&await p.getByText('정산금',{exact:true}).count()===0,'실제 dev 홈 추이 요청을 브라우저 네트워크에서 차단해 실패시킨 뒤 차단 해제·새로고침으로 실제 응답 재조회. 무거래 매출 0원 복구 및 정산 숨김 유지. 응답 모의 처리 없음',[homeError,homeRetry]);p.off('requestfailed',failed);await cdp.detach();cdp=undefined;
    const h=await screen(p,info,`${role}-single-summary-layout`);const grid=await p.getByRole('heading',{name:/^오늘 /}).evaluate(e=>{const card=e.closest('section')!;const g=card.querySelector('.grid')!;return {columns:getComputedStyle(g).gridTemplateColumns.split(' ').length,separators:card.querySelectorAll('.absolute').length};});put('H06',grid.columns===1&&grid.separators===0,'한쪽 요약 항목은 실제 계산 스타일 1열, 구분선 요소 없음',[h]);
   }
  }
  await openPreset(a,role);await save(a,role,{'스토어 거래조회':false,'스토어 정산조회':true},'정산만 탭·단일 열 검증');await refresh(p);await settled(p);await screen(p,info,`${role}-settlement-only-layout-refresh`);await p.context().close();p=await fresh(browser,role);await go(p,'/');const singleSettlement=await screen(p,info,`${role}-settlement-only-layout`);const settlementGrid=await p.getByRole('heading',{name:/^오늘 /}).evaluate(e=>{const card=e.closest('section')!;const grid=card.querySelector('.grid')!;return {columns:getComputedStyle(grid).gridTemplateColumns.split(' ').length,separators:card.querySelectorAll('.absolute').length};});put('H06',settlementGrid.columns===1&&settlementGrid.separators===0&&await p.getByText('금일매출',{exact:true}).count()===0,'정산만 표시 시 실제 1열, 매출·구분선 요소 없음',[singleSettlement]);await go(p,'/settlement');const tabShot=await screen(p,info,`${role}-settlement-only-tabs`);record(rows,role,'정산만','N02',await p.getByRole('navigation',{name:'내역 이동 탭'}).getByRole('link',{name:'거래내역',exact:true}).count()===0,'정산만 권한에서 거래내역 탭 미표시',[tabShot]);persist(role,rows);
  await toggle(true);await go(p,'/');cdp=await p.context().newCDPSession(p);await cdp.send('Network.enable');await cdp.send('Network.emulateNetworkConditionsByRule',{offline:false,matchedNetworkConditions:[{urlPattern:'https://api-dev.biscuitpay.com/api/v1/store/transactions/trend*',latency:6000,downloadThroughput:-1,uploadThroughput:-1}]});
  const delayedRequest=p.waitForRequest(r=>new URL(r.url()).pathname==='/api/v1/store/transactions/trend',{timeout:60000});await Promise.all([p.reload({waitUntil:'domcontentloaded'}),delayedRequest]);
  await openPreset(a,role);await save(a,role,{'스토어 거래조회':true,'스토어 정산조회':false},'실제 지연 요청 중 정산 OFF');await refresh(p);await settled(p);await p.waitForTimeout(7000);const delayedShot=await screen(p,info,`${role}-delayed-real-request-after-permission-save-refresh`);const hidden=await p.getByText('정산금',{exact:true}).count()===0&&await p.getByText('총 정산예정금',{exact:true}).count()===0;
  await cdp.send('Network.emulateNetworkConditions',{offline:false,latency:0,downloadThroughput:-1,uploadThroughput:-1});await cdp.detach();cdp=undefined;await p.context().close();p=await fresh(browser,role);await go(p,'/');const slowFresh=await screen(p,info,`${role}-delayed-case-fresh-login`);put('C03',hidden&&await p.getByText('정산금',{exact:true}).count()===0,'실제 개발계 요청을 브라우저에서 6초 지연. 요청 중 권한 OFF 저장 → 기존 브라우저 새로고침 → 지연 종료 후 미표시 → 재로그인 확인. 응답 대체 없음',[delayedShot,slowFresh]);
 }catch(error){if(/Timeout|ERR_CONNECTION|ERR_HTTP2/.test(String(error)))throw new Error(`BLOCKED: ${String(error)}`);throw error;}finally{
  if(cdp)await cdp.send('Network.emulateNetworkConditionsByRule',{offline:false,matchedNetworkConditions:[]}).catch(()=>{});if(cdp)await cdp.send('Network.setBlockedURLs',{urls:[]}).catch(()=>{});if(cdp)await cdp.send('Network.emulateNetworkConditions',{offline:false,latency:0,downloadThroughput:-1,uploadThroughput:-1}).catch(()=>{});
  try{await openPreset(a,role);await save(a,role,{'스토어 거래조회':selected(original).includes(keys['스토어 거래조회']),'스토어 정산조회':selected(original).includes(keys['스토어 정산조회'])},'전월 데이터 검증 원복');expect(selected(await preset(c,role))).toEqual(selected(original));await refresh(p);await settled(p);const s=await screen(p,info,`${role}-data-restored`);record(rows,role,'data restore',['F01','F02','F03','F04'],true,'전체 원본 권한 복구 후 스토어 새로고침',[s]);persist(role,rows);}finally{await p.context().close();await c.close();}
 }
 // All server permissions are restored before switching identity in the same browser.
 const next:Role=role==='CO'?'BO':role==='BO'?'VE':'CO';const q=await fresh(browser,role);
 try{await go(q,'/');await q.getByRole('button',{name:'사이드바 열기'}).click();await q.getByRole('button',{name:'로그아웃',exact:true}).click();await q.getByRole('button',{name:'로그아웃',exact:true}).last().click();await q.waitForURL('**/sign-in');await loginWithCredentials(q,storeEnv(next));await go(q,'/');const s=await screen(q,info,`${role}-switch-to-${next}`);const response=await q.context().request.get('https://api-dev.biscuitpay.com/api/v1/store/users/auth');const payload=(await response.json()).payload;const expected=JSON.parse(fs.readFileSync(`${recovery}/${next}-user-detail.json`,'utf8')).payload;put('C04',payload.userId===expected.id&&(await q.getByText('금일매출',{exact:true}).count()>0)===selected(JSON.parse(fs.readFileSync(`${recovery}/${next}-baseline.json`,'utf8'))).includes('STORE_READ_TRANSACTION'),`동일 브라우저 로그아웃 → ${next} 새 로그인, 실제 인증 사용자 및 권한별 홈 일치`,[s]);}finally{await q.context().close();persist(role,rows);}
});
