from __future__ import annotations

import json
from pathlib import Path
from playwright.sync_api import sync_playwright

HERE = Path(__file__).resolve().parent
WWW = HERE.parent.parent / 'www'


def _replace_between(text: str, start: str, end: str, replacement: str) -> str:
    i = text.index(start)
    j = text.index(end, i)
    return text[:i] + replacement + '\n\n' + text[j:]


def _prepare_record() -> str:
    text = (HERE / 'harness.js').read_text()
    prod = (WWW / 'script.js').read_text()
    text = text.replace("const fs = require('fs');\nconst path = require('path');\nconst { JSDOM } = require('jsdom');\n\nconst WWW = path.resolve(__dirname, '..', '..', 'www');\nconst script = fs.readFileSync(path.join(WWW, 'script.js'), 'utf8');", f"const script = {json.dumps(prod)};")
    replacement = r'''async function makeDom() {
    const frame = document.createElement('iframe');
    document.body.appendChild(frame);
    const w = frame.contentWindow;
    w.document.open();
    w.document.write(`<!DOCTYPE html><body>
        <main id="mainContent">
            <p id="itemA">jeden</p>
            <p id="itemB">dwa</p>
        </main>
        <footer>
            <div id="cancelZone">x</div>
            <div><button id="recordBtn"></button></div>
            <div id="lockZone">lock</div>
        </footer>
    </body>`);
    w.document.close();

    const log = { started: 0, stopped: 0, tracksStopped: 0, sent: 0, forms: [], urls: [] };
    for (const [id, rect] of Object.entries(RECTS)) {
        const el = w.document.getElementById(id);
        el.getBoundingClientRect = () => {
            if (id === 'recordBtn' && el.style.position === 'fixed') {
                const left = parseFloat(el.style.left);
                const top = parseFloat(el.style.top);
                return { left, top, right: left + 80, bottom: top + 80, width: 80, height: 80 };
            }
            return rect;
        };
    }
    w.document.elementFromPoint = (x, y) => {
        if (y >= 450) return null;
        return w.document.getElementById(x < 200 ? 'itemA' : 'itemB');
    };
    w.MediaRecorder = class {
        constructor() { this.state = 'inactive'; this.stream = { getTracks: () => [{ stop: () => log.tracksStopped++ }] }; }
        start() { this.state = 'recording'; log.started++; this.onstart?.(); }
        stop() { this.state = 'inactive'; log.stopped++; setTimeout(() => this.onstop?.(), 0); }
    };
    Object.defineProperty(w.navigator, 'mediaDevices', { configurable: true, value: { getUserMedia: async () => ({ getTracks: () => [] }) } });
    w.Blob = class {};
    w.FormData = class { constructor() { this.entries = []; } append(key, value) { this.entries.push([key, value]); } };
    w.fetch = async (url, options) => { log.sent++; log.urls.push(url); log.forms.push(options?.body?.entries ?? []); return {}; };
    w.Element.prototype.animate = function () { const anim = {}; queueMicrotask(() => anim.onfinish?.()); return anim; };
    let clock = 0;
    Object.defineProperty(w.performance, 'now', { configurable: true, value: () => clock });
    const liveListeners = [];
    const carousel = { currentHash: 'hash-A', justSwiped: false, onLiveChange: (listener) => liveListeners.push(listener) };
    w.pageCarousel = carousel;
    w.pinEndpoints = { pin: '1234', uploadUrl: UPLOAD_URL, socketUrl: 'wss://test.invalid/gui-socket/1234' };
    let fired = 0;
    w.addEventListener('DOMContentLoaded', () => fired++);
    w.eval(script);
    await tick();
    if (fired === 0) w.document.dispatchEvent(new w.Event('DOMContentLoaded', { bubbles: true }));
    if (fired > 1) throw new Error(`DOMContentLoaded odpalony ${fired} razy — podwójna inicjalizacja`);
    return {
        window: w, log, carousel, advance: (ms) => { clock += ms; },
        swipeTo: (hash) => { carousel.currentHash = hash; liveListeners.forEach((listener) => listener(hash)); },
        notifyLive: (hash) => liveListeners.forEach((listener) => listener(hash)),
        ...helpers(w),
    };
}'''
    text = _replace_between(text, 'async function makeDom()', 'const tick =', replacement)
    text = text.replace("console.log(JSON.stringify({ error: error.message, stack: error.stack, results }));\n        process.exit(2);", "window.__HARNESS_RESULT__ = { error: error.message, stack: error.stack, results }; return;")
    text = text.replace("console.log(JSON.stringify({ results }));\n    process.exit(results.every(r => r.ok) ? 0 : 1);", "window.__HARNESS_RESULT__ = { results };")
    return text


def _prepare_carousel() -> str:
    text = (HERE / 'carousel-harness.js').read_text()
    prod = (WWW / 'page-carousel.js').read_text()
    text = text.replace("const fs = require('fs');\nconst path = require('path');\nconst { JSDOM } = require('jsdom');\n\nconst WWW = path.resolve(__dirname, '..', '..', 'www');\nconst source = fs.readFileSync(path.join(WWW, 'page-carousel.js'), 'utf8');", f"const source = {json.dumps(prod)};")
    replacement = r'''function makeCarousel() {
    const frame = document.createElement('iframe');
    document.body.appendChild(frame);
    const w = frame.contentWindow;
    w.document.open();
    w.document.write(`<!DOCTYPE html><body><div class="container"><main id="mainContent"></main></div></body>`);
    w.document.close();
    w.eval(source);
    const content = w.document.getElementById('mainContent');
    const renders = [];
    const live = [];
    const carousel = w.createPageCarousel({
        content,
        render: (page) => { renders.push(page.hash); content.innerHTML = page.html ?? ''; },
    });
    carousel.onLiveChange((hash) => live.push(hash));
    const track = w.document.querySelector('.carousel-track');
    async function settle() {
        for (let i = 0; i < 6; i++) {
            const event = new w.Event('transitionend');
            Object.defineProperty(event, 'propertyName', { value: 'transform' });
            track.dispatchEvent(event);
            await tick();
        }
    }
    return {
        window: w, carousel, content, renders, live, settle,
        push: async (hash, replaces = null) => { carousel.push({ html: `<div>${hash}</div>`, js: '', hash, replaces }); await settle(); },
        hashes: () => ({ index: carousel.index, length: carousel.length, current: carousel.currentHash }),
    };
}'''
    text = _replace_between(text, 'function makeCarousel()', '// ---------------------------------------------------------------- przypadki', replacement)
    text = text.replace("console.log(JSON.stringify({ error: error.message, stack: error.stack, results }));\n        process.exit(2);", "window.__HARNESS_RESULT__ = { error: error.message, stack: error.stack, results }; return;")
    text = text.replace("console.log(JSON.stringify({ results }));\n    process.exit(results.every(r => r.ok) ? 0 : 1);", "window.__HARNESS_RESULT__ = { results };")
    return text


def run(harness: str) -> dict:
    if harness == 'harness.js': js = _prepare_record()
    elif harness == 'carousel-harness.js': js = _prepare_carousel()
    else: raise ValueError(harness)
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True, executable_path='/usr/bin/chromium', args=['--no-sandbox'])
        page = browser.new_page()
        page.set_content('<!doctype html><body></body>')
        page.evaluate(js)
        page.wait_for_function('window.__HARNESS_RESULT__ !== undefined', timeout=120000)
        result = page.evaluate('window.__HARNESS_RESULT__')
        browser.close()
        return result

if __name__ == '__main__':
    import sys
    print(json.dumps(run(sys.argv[1])))
