"""Acceptance tests for the deliberately incomplete fictional exercise.

The baseline is expected to fail these three tests. They describe the desired
behavior; do not weaken them to make the exercise appear finished.
"""

from __future__ import annotations

import http.cookiejar
import json
import tempfile
import threading
import unittest
from html.parser import HTMLParser
from pathlib import Path
from urllib.error import HTTPError
from urllib.parse import urlencode
from urllib.request import HTTPCookieProcessor, Request, build_opener

from app import DRAFT_ID, SEED_TEXT, create_server


class PageSnapshot(HTMLParser):
    """Read visible form state without depending on styling or exact copy."""

    def __init__(self, document: str):
        super().__init__()
        self.textarea = ""
        self.buttons = []
        self.statuses = []
        self.alerts = []
        self._capture = None
        self._parts = []
        self.feed(document)

    def handle_starttag(self, tag, attrs):
        role = dict(attrs).get("role")
        if tag == "textarea":
            self._capture, self._parts = "textarea", []
        elif tag == "button":
            self._capture, self._parts = "button", []
        elif role in ("status", "alert"):
            self._capture, self._parts = role, []

    def handle_data(self, data):
        if self._capture is not None:
            self._parts.append(data)

    def handle_endtag(self, tag):
        if self._capture == "textarea" and tag == "textarea":
            self.textarea = "".join(self._parts)
        elif self._capture == "button" and tag == "button":
            self.buttons.append("".join(self._parts).strip())
        elif self._capture == "status" and tag in ("p", "div"):
            self.statuses.append("".join(self._parts).strip())
        elif self._capture == "alert" and tag in ("p", "div"):
            self.alerts.append("".join(self._parts).strip())
        else:
            return
        self._capture, self._parts = None, []


class SaveDraftAcceptance(unittest.TestCase):
    def setUp(self):
        self.temp = tempfile.TemporaryDirectory()
        self.server = create_server(0, Path(self.temp.name) / "exercise.sqlite3")
        self.thread = threading.Thread(target=self.server.serve_forever, daemon=True)
        self.thread.start()
        self.base = f"http://127.0.0.1:{self.server.server_port}"
        self.client = build_opener(HTTPCookieProcessor(http.cookiejar.CookieJar()))

    def tearDown(self):
        self.server.shutdown()
        self.server.server_close()
        self.thread.join(timeout=2)
        self.temp.cleanup()

    def request(self, path, form=None):
        data = urlencode(form).encode() if form is not None else None
        req = Request(self.base + path, data=data)
        if data is not None:
            req.add_header("Content-Type", "application/x-www-form-urlencoded")
        try:
            with self.client.open(req) as response:
                return response.status, response.read().decode("utf-8")
        except HTTPError as error:
            return error.code, error.read().decode("utf-8")

    def choose(self, identity):
        status, _ = self.request(f"/as/{identity}")
        self.assertEqual(status, 200)  # urllib follows the local redirect.

    def test_author_recovers_only_confirmed_saved_text_after_return(self):
        self.choose("author")
        new_text = "The imaginary library now opens on Friday."
        status, save_page = self.request("/save", {"text": new_text})
        self.assertEqual(status, 200)
        self.assertTrue(
            any("saved" in message.lower() for message in PageSnapshot(save_page).statuses),
            "A successful save should confirm the persisted text.",
        )

        _, reloaded = self.request("/")
        self.assertEqual(PageSnapshot(reloaded).textarea, new_text, "A confirmed save must survive reload.")
        self.request("/logout")
        self.choose("author")
        _, returned = self.request("/")
        self.assertEqual(PageSnapshot(returned).textarea, new_text, "The author must recover the saved text after returning.")

    def test_second_identity_cannot_read_author_draft_in_ui_or_direct_endpoint(self):
        self.choose("author")
        author_status, author_body = self.request(f"/api/drafts/{DRAFT_ID}")
        self.assertEqual(author_status, 200, "The fixture must exist for this privacy check.")
        self.assertEqual(json.loads(author_body)["content"], SEED_TEXT)

        self.choose("reviewer")
        _, reviewer_page = self.request("/")
        self.assertNotEqual(PageSnapshot(reviewer_page).textarea, SEED_TEXT)
        direct_status, direct_body = self.request(f"/api/drafts/{DRAFT_ID}")
        self.assertIn(direct_status, (403, 404), "A second user must be denied at the endpoint.")
        self.assertNotIn(SEED_TEXT, direct_body, "A denial must not leak the author's text.")

    def test_failed_save_keeps_edit_visible_and_does_not_replace_confirmed_draft(self):
        self.choose("author")
        unsaved = "An unsaved change to the imaginary library schedule."
        status, failed_page = self.request(
            "/save", {"text": unsaved, "simulate_failure": "1"}
        )
        self.assertEqual(status, 503, "The deterministic failure must not claim success.")
        failed = PageSnapshot(failed_page)
        self.assertEqual(failed.textarea, unsaved, "Keep the edit visible for retry in the current page.")
        self.assertTrue(failed.alerts, "A failed save needs a visible error message.")
        self.assertTrue(any("retry" in button.lower() for button in failed.buttons), "Offer an explicit Retry button.")
        self.assertFalse(
            any("saved" in message.lower() for message in failed.statuses),
            "Never show a success state for a failed save.",
        )

        _, reloaded = self.request("/")
        self.assertEqual(PageSnapshot(reloaded).textarea, SEED_TEXT, "Reload restores the last confirmed version.")

        retry_status, _ = self.request("/save", {"text": unsaved})
        self.assertEqual(retry_status, 200)
        _, after_retry = self.request("/")
        self.assertEqual(PageSnapshot(after_retry).textarea, unsaved, "A successful retry must confirm and persist the edit.")


if __name__ == "__main__":
    unittest.main(verbosity=2)
