# ruff: noqa: F821, I001

from __future__ import annotations

import asyncio
import hashlib
import json
from typing import TYPE_CHECKING

if TYPE_CHECKING: from router import Event

HELPER = "0123456789abcdef0123456789abcdef"
TEXT = app.config.fields().add("text", app.config.field("string").bounds(1, 800).required())

class Module:
    handler_timeout = 120
    config = app.config.fields().add("history", app.config.field("integer").bounds(1, 100).default(10))
    pages = [app.ui.define("main", {"ru": "Отчёты", "en": "Reports"})
             .add(app.ui.element("status", "text", "Status"))
             .add(app.ui.element("count", "metric", "Reports"))
             .add(app.ui.element("history", "table", "History", columns=["number", "bytes", "sha256"]))
             .add(app.ui.element("collect", "form", "Create report", action="collect"))
             .add(app.ui.element("schedule", "form", "Schedule report", action="schedule"))
             .add(app.ui.element("cancel", "button", "Cancel schedule", action="cancel"))]

    def __init__(self) -> None: self.lock = asyncio.Lock()

    async def publish(self) -> None:
        config = await app.config.snapshot().load()
        history = [value async for _, value in app.db.scope("history/").items()]
        history.sort(key=lambda item: item["number"])
        async with app.ui.page("main") as page:
            page.text("status", "Ready").metric("count", await app.db.get("count", 0))
            table = page.table("history").clear()
            for value in history[-config.get("history"):]:
                table.row(**{key: value[key] for key in ("number", "bytes", "sha256")})

    async def process(self, text: str) -> dict:
        async with self.lock:
            helper = app.modules(HELPER)
            call = await helper.function("digest", text=text).start()
            log = app.logs.bind(operation="report")
            async with log.timer("Report created"):
                response = await app.web.session("https://postman-echo.com/").post("post", {"text": text})
                returned = response.raise_for_status().json()["json"]["text"]
                if returned != text: raise ValueError("The service changed the report text")
                checked = await call.wait()
                if checked["sha256"] != hashlib.sha256(text.encode()).hexdigest(): raise ValueError("Invalid helper checksum")
                raw = json.dumps({"text": text, **checked}, ensure_ascii=False).encode()
                async with app.files.writer("report.json", len(raw), hashlib.sha256(raw).hexdigest(), ttl=3600) as writer:
                    for offset in range(0, len(raw), 127): await writer.write(raw[offset:offset + 127])
                file, committed = writer.file, False
                try:
                    if (await file.json())["text"] != text: raise ValueError("Invalid report file")
                    async with await file.copy("upload.json", ttl=60) as copy:
                        uploaded = await app.web("https://postman-echo.com/post", "POST").header("Content-Type", "application/json").upload(copy)
                        if uploaded.raise_for_status().json()["json"]["text"] != text: raise ValueError("Invalid uploaded report")
                    count = await app.db.record("count", 0).load()
                    count.increment(())
                    number = count.get()
                    result = {"number": number, "file": file.id, **checked}
                    await app.db.batch().set("count", number, revision=count.revision).set(f"history/{number:08}", result).set("latest", result).commit()
                    committed = True
                    await self.publish()
                    await helper.emit("received", {"text": text})
                    return result
                finally:
                    if not committed: await file.delete()

    @loader.action(fields=TEXT, access="owner")
    async def collect(self, ctx: Event) -> dict: return await self.process(ctx.values["text"])

    @loader.callback(fields=TEXT, access="owner")
    async def pressed(self, ctx: Event) -> None:
        result = await self.process(ctx.values["text"])
        await ctx.edit(f"Report #{result['number']}: {result['bytes']} bytes")

    @loader.command("report", access="owner", source="both")
    async def report(self, ctx: Event) -> None:
        keyboard = app.inline.keyboard([[app.inline.button("Create report", self.pressed, args={"text": ctx.args or "Button report"})]])
        await app.inline(ctx).form(text="Reports", keyboard=keyboard, ttl=300)

    @loader.action(fields=TEXT, access="owner")
    async def schedule(self, ctx: Event) -> dict:
        async with self.lock:
            previous = await app.db.get("scheduled")
            if previous:
                async for task in app.tasks.iter("pending"):
                    if task.id == previous: return {"task": task.id}
            task = await app.tasks(self.tick, values=ctx.values).after(10).create()
            await app.db.set("scheduled", task.id)
            return {"task": task.id}

    @loader.action(access="owner")
    async def cancel(self, ctx: Event) -> dict:
        id = await app.db.get("scheduled")
        if id is None: return {"state": "absent"}
        task = await app.tasks.task(id).load()
        await task.cancel()
        await task.delete()
        await app.db.delete("scheduled")
        return {"state": "cancelled"}

    @loader.task(fields=TEXT)
    async def tick(self, ctx: Event) -> dict: return await self.process(ctx.values["text"])

    @loader.request(fields=TEXT)
    async def receive(self, ctx: Event) -> dict: return await self.process(ctx.values["text"])
