/* eslint-disable */
/**
 * Champion / Manager workflow E2E (the "send to department" lifecycle):
 *   PX-team sends -> champion investigates (creates Qs -> staff answers) ->
 *   champion writes response -> dept manager approves/rejects ->
 *   PX-team accepts/rejects -> PX-team resolves.
 *
 * Drives the real HTTP endpoints the UI calls (AJAX + form POSTs) and asserts
 * state transitions via the `get_e2e_workflow_state` CLI helper (Node can't read
 * the Django DB). Run-to-completion style: every step guarded, failures recorded
 * as observations, never aborts the suite.
 *
 * Run headed:
 *   E2E_TIMEOUT=120000 E2E_ACTION_TIMEOUT=12000 E2E_NAV_TIMEOUT=20000 \
 *     npx playwright test --headed --project chromium champion-manager-workflow --workers=1
 */
import { test } from '@playwright/test';
import { execSync } from 'child_process';
import * as path from 'path';
import {
  attachObservers, bodyHasTraceback, loginAndScope, observe, OBS, BASE_URL,
} from '../../helpers/audit';
import { RoleName } from '../../helpers/helpers';

const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..');

const M = 'ChampionManagerWorkflow';
const PXT: RoleName = 'hospital_admin';   // PX-team actor
const CHAMP: RoleName = 'champion';        // department champion
const MGR: RoleName = 'dept_manager';      // department manager (reviewer)

type State = Record<string, string>;

function shell(cmd: string): string {
  return execSync(cmd, { cwd: PROJECT_ROOT }).toString();
}

function uv(args: string): string {
  return shell(`uv run manage.py ${args}`);
}

function seedComplaint(): { cid: string; deptId: string; staffId: string; championStaffId: string } {
  const out = uv('seed_e2e_complaint');
  const m = out.match(/complaint_id=(\S+)\s+reference=(\S+)\s+department_id=(\S+)\s+staff_id=(\S+)\s+champion_staff_id=(\S+)/);
  if (!m) throw new Error('seed_e2e_complaint parse failed: ' + out);
  return { cid: m[1], deptId: m[3], staffId: m[4], championStaffId: m[5] };
}

function workflowState(cid: string): State {
  const out = uv(`get_e2e_workflow_state ${cid}`);
  const s: State = {};
  for (const line of out.split('\n')) {
    const i = line.indexOf('=');
    if (i > 0) s[line.slice(0, i)] = line.slice(i + 1);
  }
  return s;
}

function csrfOf(page: import('@playwright/test').Page): Promise<string> {
  return page.context().cookies().then((c) => c.find((x) => x.name === 'csrftoken')?.value || '');
}

/** POST url-encoded form data using the page session (shared cookies). */
async function postForm(page: import('@playwright/test').Page, url: string, data: Record<string, string>) {
  const csrf = await csrfOf(page);
  return page.context().request.post(url, {
    maxRedirects: 0,
    headers: { 'X-Requested-With': 'XMLHttpRequest', ...(csrf ? { 'X-CSRFToken': csrf } : {}) },
    form: { csrfmiddlewaretoken: csrf, ...data },
  });
}

/** POST and observe the HTTP status (used where we need to see a step's outcome). */
async function postObs(page: import('@playwright/test').Page, url: string, data: Record<string, string>, step: string, role?: string) {
  const r = await postForm(page, url, data);
  const loc = r.headers()['location'] || '';
  const loginBounce = loc.includes('/accounts/login');
  observe(M, step, r.status() < 400 && !loginBounce ? 'PASS' : 'FAIL',
    `HTTP ${r.status()}${loc ? ` -> ${loc.slice(0, 60)}` : ''}${loginBounce ? ' (auth bounce!)' : ''}`, { role, http: r.status() });
  return r;
}

/** Ensure the page is actually authenticated as `role` before POSTing; re-login if bounced. */
async function ensureAuth(page: import('@playwright/test').Page, role: RoleName) {
  // probe an auth-required page via the APIRequestContext
  const probe = await page.context().request.get(`${BASE_URL}/complaints/?__probe=1`, { maxRedirects: 0 });
  if (probe.status() === 302 && (probe.headers()['location'] || '').includes('/accounts/login')) {
    await login(page, role);  // re-establish session
  }
}

async function login(page: import('@playwright/test').Page, role: RoleName) {
  await page.context().clearCookies();
  await loginAndScope(page, role, M);
}

const assertState = (cid: string, want: Record<string, string>, step: string, role?: string) => {
  const s = workflowState(cid);
  for (const [k, v] of Object.entries(want)) {
    const got = s[k];
    const ok = got === v;
    observe(M, step, ok ? 'PASS' : 'FAIL', `state ${k}=${got} (want ${v})`, { role });
  }
};

// ===========================================================================
// FLOW A — logged-in happy path: send -> champion response -> mgr approve -> PX accept -> resolve
// ===========================================================================
test.describe('Champion/Manager workflow', () => {
  test.describe.configure({ mode: 'serial' });

  test('Flow A: full happy path (PX -> champion -> manager -> PX -> resolve)', async ({ page }) => {
    attachObservers(page, M, PXT);
    let cid = '';
    try {
      const seed = seedComplaint();
      cid = seed.cid;
      observe(M, 'A-seed', 'INFO', `complaint ${cid} (dept ${seed.deptId})`, { role: PXT });

      // 1. PX sends to department (AJAX) - requires a contact_person_id (a dept role-holder = champion)
      await login(page, PXT);
      const send = await postForm(page, `${BASE_URL}/complaints/${cid}/send-to/`, {
        recipient_type: 'department', department_id: seed.deptId, contact_person_id: seed.championStaffId, note: 'E2E send to dept',
      });
      observe(M, 'A-send', send.status() === 200 ? 'PASS' : 'WARN', `send-to HTTP ${send.status()}`, { role: PXT, http: send.status() });
      assertState(cid, { idept_sent: 'True' }, 'A-send-state', PXT);
      const ideptId = workflowState(cid).involved_department_id;

      // activate to in_progress (for resolve later)
      await postForm(page, `${BASE_URL}/complaints/${cid}/activate/`, {});

      // 2. Champion submits response (logged-in modal endpoint)
      await login(page, CHAMP);
      const resp = await postForm(page, `${BASE_URL}/complaints/departments/${ideptId}/response/`, {
        response_notes_en: 'E2E champion response (Flow A)',
      });
      observe(M, 'A-champion-response', resp.status() < 400 ? 'PASS' : 'FAIL', `response HTTP ${resp.status()}`, { role: CHAMP, http: resp.status() });
      assertState(cid, { idept_response_submitted: 'True', idept_manager_review_status: 'pending' }, 'A-champion-state', CHAMP);

      // 3. Department manager approves
      await login(page, MGR);
      const appr = await postForm(page, `${BASE_URL}/organizations/departments/${seed.deptId}/manager-review/${ideptId}/`, {
        review_action: 'approve',
      });
      observe(M, 'A-manager-approve', appr.status() < 400 ? 'PASS' : 'FAIL', `manager-review HTTP ${appr.status()}`, { role: MGR, http: appr.status() });
      assertState(cid, { idept_manager_review_status: 'approved' }, 'A-manager-state', MGR);

      // 4. PX accepts the response
      await login(page, PXT);
      const acc = await postForm(page, `${BASE_URL}/complaints/departments/${ideptId}/review-response/`, {
        acceptance_status: 'acceptable',
      });
      observe(M, 'A-px-accept', acc.status() < 400 ? 'PASS' : 'FAIL', `review-response HTTP ${acc.status()}`, { role: PXT, http: acc.status() });
      assertState(cid, { idept_acceptance_status: 'acceptable' }, 'A-accept-state', PXT);

      // 5. PX resolves with resolution notes
      const resolve = await postForm(page, `${BASE_URL}/complaints/${cid}/change-status/`, {
        status: 'resolved', resolution: 'E2E resolution notes (Flow A)',
      });
      observe(M, 'A-resolve', resolve.status() < 400 ? 'PASS' : 'FAIL', `change-status HTTP ${resolve.status()}`, { role: PXT, http: resolve.status() });
      assertState(cid, { complaint_status: 'resolved' }, 'A-resolve-state', PXT);

      const finalTb = await bodyHasTraceback(page);
      if (finalTb) observe(M, 'A-traceback', 'FAIL', finalTb, { role: PXT });
    } catch (e) {
      observe(M, 'A-flow', 'FAIL', `exception: ${(e as Error).message}`, { role: PXT });
    }
  });

  // -------------------------------------------------------------------
  // FLOW B — token investigation sub-flow: champion creates Qs -> staff answers -> champion reviews
  // -------------------------------------------------------------------
  test('Flow B: token investigation (champion Qs -> staff answers -> champion review)', async ({ page }) => {
    attachObservers(page, M, CHAMP);
    let cid = '';
    try {
      const seed = seedComplaint();
      cid = seed.cid;
      observe(M, 'B-seed', 'INFO', `complaint ${cid}`, { role: CHAMP });

      // 1. PX sends via send_to_department_form (generates champion ComplaintExplanation token)
      await login(page, PXT);
      const send = await postForm(page, `${BASE_URL}/complaints/${cid}/send-to-department/`, {
        selected_departments: seed.deptId, request_message: 'E2E investigate please', action: 'send',
      });
      observe(M, 'B-send-dept-form', send.status() < 400 ? 'PASS' : 'FAIL', `send-to-department HTTP ${send.status()}`, { role: PXT, http: send.status() });

      const s0 = workflowState(cid);
      const champToken = s0.explanation_token;
      if (!champToken || champToken === 'NONE') {
        observe(M, 'B-champion-token', 'FAIL', 'no explanation token generated', { role: CHAMP });
        return;
      }
      observe(M, 'B-champion-token', 'PASS', `token ${champToken.slice(0, 8)}...`, { role: CHAMP });

      // 2. Champion starts investigation (token URL, no auth): create questions + select accused staff
      await page.context().clearCookies();
      await page.goto(`${BASE_URL}/complaints/${cid}/investigate/${champToken}/`);
      await page.waitForLoadState('domcontentloaded');
      const tb1 = await bodyHasTraceback(page);
      if (tb1) { observe(M, 'B-investigate-open', 'FAIL', `traceback: ${tb1}`); return; }

      // add a question
      const q = page.locator('input[name="questions[]"]').first();
      if (await q.count()) await q.fill('E2E investigation question?');
      // select the accused staff checkbox matching our staff id
      const staffBox = page.locator(`input[name="accused_staff[]"][value="${seed.staffId}"]`).first();
      if (await staffBox.count()) await staffBox.check();
      else observe(M, 'B-accused-staff', 'WARN', 'accused_staff checkbox not found; proceeding questions-only', { role: CHAMP });

      await page.locator('button[type="submit"], input[type="submit"]').first().click().catch(() => {});
      await page.waitForLoadState('networkidle', { timeout: 8000 }).catch(() => {});
      await page.waitForTimeout(1500);
      observe(M, 'B-investigate-submit', page.url().includes('investigate') ? 'PASS' : 'WARN', `post-submit url ${page.url()}`, { role: CHAMP });

      // 3. Staff answers (token URL)
      const s1 = workflowState(cid);
      const staffTokLine = Object.keys(s1).find((k) => k.startsWith('investigation_token_staff_'));
      if (!staffTokLine || s1[staffTokLine] === 'completed' || s1[staffTokLine] === 'NONE') {
        observe(M, 'B-staff-token', 'SKIP', 'no staff answer token (staff may not have been selected)', { role: CHAMP });
      } else {
        const staffToken = s1[staffTokLine];
        await page.goto(`${BASE_URL}/complaints/${cid}/investigate/respond/${staffToken}/`);
        await page.waitForLoadState('domcontentloaded');
        const tb2 = await bodyHasTraceback(page);
        if (tb2) { observe(M, 'B-staff-respond-open', 'FAIL', `traceback: ${tb2}`); }
        else {
          // answer every question textarea
          const answers = page.locator('textarea[name^="question_"]');
          const n = await answers.count();
          for (let i = 0; i < n; i++) await answers.nth(i).fill(`E2E staff answer ${i + 1}`).catch(() => {});
          await page.locator('button[type="submit"], input[type="submit"]').first().click().catch(() => {});
          await page.waitForLoadState('networkidle', { timeout: 8000 }).catch(() => {});
          await page.waitForTimeout(1500);
          observe(M, 'B-staff-respond', n > 0 ? 'PASS' : 'WARN', `answered ${n} questions`, { role: 'staff' });
        }
      }

      // 4. Champion reviews answers + writes final reply (same explanation token)
      await page.goto(`${BASE_URL}/complaints/${cid}/investigate/review/${champToken}/`);
      await page.waitForLoadState('domcontentloaded');
      const tb3 = await bodyHasTraceback(page);
      if (tb3) { observe(M, 'B-review-open', 'FAIL', `traceback: ${tb3}`); return; }
      const fr = page.locator('textarea[name="final_reply"]').first();
      if (await fr.count()) {
        await fr.fill('E2E champion final reply (Flow B)');
        await page.locator('button[type="submit"], input[type="submit"]').first().click().catch(() => {});
        await page.waitForLoadState('networkidle', { timeout: 8000 }).catch(() => {});
        await page.waitForTimeout(1500);
      }
      assertState(cid, { idept_response_submitted: 'True', idept_manager_review_status: 'pending' }, 'B-review-state', CHAMP);
    } catch (e) {
      observe(M, 'B-flow', 'FAIL', `exception: ${(e as Error).message}`, { role: CHAMP });
    }
  });

  // -------------------------------------------------------------------
  // FLOW C1 — manager REJECT returns the complaint to the champion
  // -------------------------------------------------------------------
  test('Flow C1: manager reject -> back to champion -> re-approve -> resolve', async ({ page }) => {
    attachObservers(page, M, MGR);
    let cid = '';
    try {
      const seed = seedComplaint();
      cid = seed.cid;
      await login(page, PXT);
      await postForm(page, `${BASE_URL}/complaints/${cid}/send-to/`, { recipient_type: 'department', department_id: seed.deptId, contact_person_id: seed.championStaffId, note: 'C1' });
      const ideptId = workflowState(cid).involved_department_id;
      await postForm(page, `${BASE_URL}/complaints/${cid}/activate/`, {});

      await login(page, CHAMP);
      await postForm(page, `${BASE_URL}/complaints/departments/${ideptId}/response/`, { response_notes_en: 'C1 first response' });
      assertState(cid, { idept_response_submitted: 'True' }, 'C1-response-state', CHAMP);

      // manager REJECTS
      await login(page, MGR);
      const rej = await postForm(page, `${BASE_URL}/organizations/departments/${seed.deptId}/manager-review/${ideptId}/`, {
        review_action: 'reject', rejection_reason: 'E2E: needs more detail',
      });
      observe(M, 'C1-manager-reject', rej.status() < 400 ? 'PASS' : 'FAIL', `reject HTTP ${rej.status()}`, { role: MGR, http: rej.status() });
      // rejection must reset response_submitted and clear manager status
      const sAfterReject = workflowState(cid);
      observe(M, 'C1-reject-reset', sAfterReject.idept_response_submitted === 'False' ? 'PASS' : 'FAIL',
        `after reject: response_submitted=${sAfterReject.idept_response_submitted} (want False)`, { role: MGR });

      // champion re-responds -> manager approves -> PX accepts -> resolve
      await login(page, CHAMP); await ensureAuth(page, CHAMP);
      await postObs(page, `${BASE_URL}/complaints/departments/${ideptId}/response/`, { response_notes_en: 'C1 revised response' }, 'C1-re-response', CHAMP);
      await login(page, MGR); await ensureAuth(page, MGR);
      await postObs(page, `${BASE_URL}/organizations/departments/${seed.deptId}/manager-review/${ideptId}/`, { review_action: 'approve' }, 'C1-re-approve', MGR);
      await login(page, PXT); await ensureAuth(page, PXT);
      await postObs(page, `${BASE_URL}/complaints/departments/${ideptId}/review-response/`, { acceptance_status: 'acceptable' }, 'C1-accept', PXT);
      await postObs(page, `${BASE_URL}/complaints/${cid}/change-status/`, { status: 'resolved', resolution: 'C1 resolved' }, 'C1-resolve', PXT);
      assertState(cid, { complaint_status: 'resolved', idept_acceptance_status: 'acceptable' }, 'C1-final-state', PXT);
    } catch (e) {
      observe(M, 'C1-flow', 'FAIL', `exception: ${(e as Error).message}`, { role: MGR });
    }
  });

  // -------------------------------------------------------------------
  // FLOW C2 — PX NOT-ACCEPTABLE returns the complaint to the champion
  // -------------------------------------------------------------------
  test('Flow C2: PX not-acceptable -> back to champion -> re-accept -> resolve', async ({ page }) => {
    attachObservers(page, M, PXT);
    let cid = '';
    try {
      const seed = seedComplaint();
      cid = seed.cid;
      await login(page, PXT);
      await postForm(page, `${BASE_URL}/complaints/${cid}/send-to/`, { recipient_type: 'department', department_id: seed.deptId, contact_person_id: seed.championStaffId, note: 'C2' });
      const ideptId = workflowState(cid).involved_department_id;
      await postForm(page, `${BASE_URL}/complaints/${cid}/activate/`, {});

      await login(page, CHAMP);
      await postForm(page, `${BASE_URL}/complaints/departments/${ideptId}/response/`, { response_notes_en: 'C2 first response' });
      await login(page, MGR);
      await postForm(page, `${BASE_URL}/organizations/departments/${seed.deptId}/manager-review/${ideptId}/`, { review_action: 'approve' });

      // PX rejects (not_acceptable)
      await login(page, PXT);
      const na = await postForm(page, `${BASE_URL}/complaints/departments/${ideptId}/review-response/`, {
        acceptance_status: 'not_acceptable', acceptance_notes: 'E2E: insufficient',
      });
      observe(M, 'C2-px-not-acceptable', na.status() < 400 ? 'PASS' : 'FAIL', `review-response HTTP ${na.status()}`, { role: PXT, http: na.status() });
      const sAfterNA = workflowState(cid);
      observe(M, 'C2-na-reset', sAfterNA.idept_response_submitted === 'False' ? 'PASS' : 'FAIL',
        `after not-acceptable: response_submitted=${sAfterNA.idept_response_submitted} (want False)`, { role: PXT });

      // champion re-responds -> manager approves -> PX accepts -> resolve
      await login(page, CHAMP); await ensureAuth(page, CHAMP);
      await postObs(page, `${BASE_URL}/complaints/departments/${ideptId}/response/`, { response_notes_en: 'C2 revised response' }, 'C2-re-response', CHAMP);
      await login(page, MGR); await ensureAuth(page, MGR);
      await postObs(page, `${BASE_URL}/organizations/departments/${seed.deptId}/manager-review/${ideptId}/`, { review_action: 'approve' }, 'C2-re-approve', MGR);
      await login(page, PXT); await ensureAuth(page, PXT);
      await postObs(page, `${BASE_URL}/complaints/departments/${ideptId}/review-response/`, { acceptance_status: 'acceptable' }, 'C2-accept', PXT);
      await postObs(page, `${BASE_URL}/complaints/${cid}/change-status/`, { status: 'resolved', resolution: 'C2 resolved' }, 'C2-resolve', PXT);
      assertState(cid, { complaint_status: 'resolved', idept_acceptance_status: 'acceptable' }, 'C2-final-state', PXT);
    } catch (e) {
      observe(M, 'C2-flow', 'FAIL', `exception: ${(e as Error).message}`, { role: PXT });
    }
  });
});

// Dump observations for the report
test.afterAll(async () => {
  const counts = OBS.reduce<Record<string, number>>((a, o) => ((a[o.status] = (a[o.status] || 0) + 1), a), {});
  console.log('\n=========== CHAMPION/MANAGER WORKFLOW SUMMARY ===========');
  console.log('Total observations:', OBS.length, JSON.stringify(counts));
  console.log('=========================================================\n');
});
