"""The import command: correctness, idempotency, and the dry-run guarantee."""
import json
import os
import shutil
import tempfile
from io import StringIO

from django.contrib.auth import get_user_model
from django.core.management import call_command
from django.test import TestCase, override_settings
from django.utils import timezone

from accounts.tests.test_hashers import WP_FIXTURES
from documents.models import Category, Document

User = get_user_model()
_TMP_ROOT = tempfile.mkdtemp(prefix="hartling-import-test-")

PLAIN, WP68_HASH, PHPASS_HASH = WP_FIXTURES[0]


def build_manifest(directory):
    """A small manifest plus its file tree, mirroring the extractor's output."""
    files_dir = os.path.join(directory, "files", "2026", "05")
    os.makedirs(files_dir, exist_ok=True)
    with open(os.path.join(files_dir, "agm.pdf"), "wb") as handle:
        handle.write(b"%PDF-1.4 minutes")

    manifest = {
        "project": "test-site",
        "users": [
            {
                "wp_id": "1", "username": "owner1", "email": "owner1@example.com",
                "password_hash": PHPASS_HASH, "hash_format": "phpass",
                "registered": "2020-03-01 10:00:00", "first_name": "Ann",
                "last_name": "Owner", "capabilities": 'a:1:{s:5:"owner";b:1;}',
            },
            {
                "wp_id": "2", "username": "staffer", "email": "staff@example.com",
                "password_hash": WP68_HASH, "hash_format": "wp6.8",
                "registered": "2021-06-01 10:00:00", "first_name": "Sam",
                "last_name": "Staff",
                "capabilities": 'a:1:{s:19:"hartling group staff";b:1;}',
            },
            {
                "wp_id": "3", "username": "brokenhash", "email": "b@example.com",
                "password_hash": "not-a-hash", "hash_format": "unknown",
                "registered": "2022-01-01 10:00:00", "first_name": "", "last_name": "",
                "capabilities": 'a:1:{s:5:"owner";b:1;}',
            },
        ],
        "categories": [{"key": "1", "name": "Meeting Minutes", "slug": "meeting-minutes"}],
        "documents": [
            {
                "wp_id": "100", "title": "AGM 2026 Minutes",
                "post_date": "2026-05-13 09:00:00", "status": "publish",
                "category_key": "1", "summary": "Annual meeting",
                "files": {"primary": {"relpath": "2026/05/agm.pdf", "found": True}},
            },
            {
                "wp_id": "101", "title": "Lost Document",
                "post_date": "2026-05-14 09:00:00", "status": "publish",
                "category_key": "1", "summary": "",
                "files": {"primary": {"relpath": "2026/05/gone.pdf", "found": False}},
            },
            {
                "wp_id": "102", "title": "No File At All",
                "post_date": "2026-05-15 09:00:00", "status": "draft",
                "category_key": "1", "summary": "", "files": {},
            },
        ],
        "board": {"pages": [], "candidates": []},
    }
    path = os.path.join(directory, "manifest.json")
    with open(path, "w") as handle:
        json.dump(manifest, handle)
    return path


@override_settings(PRIVATE_MEDIA_ROOT=os.path.join(_TMP_ROOT, "private"))
class ImportWordPressTests(TestCase):
    def setUp(self):
        self.workdir = tempfile.mkdtemp(dir=_TMP_ROOT)
        self.manifest = build_manifest(self.workdir)

    @classmethod
    def tearDownClass(cls):
        shutil.rmtree(_TMP_ROOT, ignore_errors=True)
        super().tearDownClass()

    def _run(self, *extra):
        out = StringIO()
        call_command("import_wordpress", "--manifest", self.manifest, *extra, stdout=out)
        return out.getvalue()

    def test_dry_run_writes_nothing(self):
        output = self._run("--dry-run")
        self.assertIn("DRY RUN", output)
        self.assertEqual(User.objects.count(), 0)
        self.assertEqual(Document.objects.count(), 0)
        self.assertEqual(Category.objects.count(), 0)

    def test_dry_run_reports_missing_files(self):
        output = self._run("--dry-run")
        self.assertIn("MISSING", output)
        self.assertIn("Lost Document", output)
        self.assertIn("reference no file", output)

    def test_import_creates_users_with_working_passwords(self):
        self._run()
        owner = User.objects.get(username="owner1")
        self.assertEqual(owner.email, "owner1@example.com")
        self.assertEqual(owner.first_name, "Ann")
        self.assertFalse(owner.is_staff)
        # The whole point of the migration: the existing password still works.
        self.assertTrue(owner.check_password(PLAIN))

    def test_staff_role_maps_to_admin_access(self):
        self._run()
        self.assertTrue(User.objects.get(username="staffer").is_staff)
        self.assertTrue(User.objects.get(username="staffer").check_password(PLAIN))

    def test_unrecognised_hash_becomes_unusable_password(self):
        self._run()
        broken = User.objects.get(username="brokenhash")
        self.assertFalse(broken.has_usable_password())
        self.assertFalse(broken.check_password("not-a-hash"))

    def test_documents_and_files_import(self):
        self._run()
        document = Document.objects.get(wp_id="100")
        self.assertEqual(document.title, "AGM 2026 Minutes")
        self.assertEqual(document.category.slug, "meeting-minutes")
        self.assertTrue(document.published)
        self.assertEqual(str(document.published_date), "2026-05-13")
        self.assertEqual(document.file.read(), b"%PDF-1.4 minutes")

    def test_documents_without_a_usable_file_are_not_created(self):
        self._run()
        self.assertFalse(Document.objects.filter(wp_id="101").exists())
        self.assertFalse(Document.objects.filter(wp_id="102").exists())
        self.assertEqual(Document.objects.count(), 1)

    def test_import_is_idempotent(self):
        self._run()
        self._run()
        self.assertEqual(User.objects.count(), 3)
        self.assertEqual(Document.objects.count(), 1)
        self.assertEqual(Category.objects.count(), 1)

    def test_bootstrap_admin_receives_the_wordpress_password(self):
        """A pre-existing account that has NEVER logged in (the container's
        bootstrap admin) must end up with its real WordPress password, not the
        generated one it was created with."""
        bootstrap = User.objects.create_user(
            username="owner1", password="generated-bootstrap-password"
        )
        self.assertIsNone(bootstrap.last_login)

        self._run()

        bootstrap.refresh_from_db()
        self.assertTrue(bootstrap.check_password(PLAIN), "WordPress password should work")
        self.assertFalse(bootstrap.check_password("generated-bootstrap-password"))

    def test_rerun_preserves_an_upgraded_password_hash(self):
        """A re-run must not drag an upgraded hash back to the legacy WP one."""
        self._run()
        owner = User.objects.get(username="owner1")
        self.assertTrue(owner.password.startswith("wp_phpass$"))

        owner.set_password(PLAIN)  # simulates the rehash-on-login upgrade
        owner.last_login = timezone.now()  # the upgrade only happens at login
        owner.save()
        self.assertTrue(owner.password.startswith("argon2"))

        self._run()
        owner.refresh_from_db()
        self.assertTrue(
            owner.password.startswith("argon2"),
            "re-import must not downgrade an already-upgraded hash",
        )
        self.assertTrue(owner.check_password(PLAIN))

    def test_rerun_still_fills_a_legacy_hash(self):
        """But a user still on a WordPress hash is refreshed normally."""
        self._run()
        self._run()
        self.assertTrue(User.objects.get(username="owner1").check_password(PLAIN))
