"""Exercise the source preflight: placeholder artwork and icon coverage.

Two distinctions decide whether these checks are usable at all, and both are
encoded here: only artwork the app REFERENCES can convict it, and an asset
catalog built from `universal` entries covers every device family from one set.
"""
import hashlib
import json
from pathlib import Path
import sys
import tempfile
import unittest

sys.path.insert(0, str(Path(__file__).resolve().parent))

import source_preflight as preflight  # noqa: E402
from test_delivery_preflight import png  # noqa: E402

STOCK = b"stock-flutter-icon-bytes"
REAL = b"the app's own finished artwork, which is quite a lot of bytes " * 64

PBXPROJ = """// !$*UTF8*$!
{{ objects = {{
  AAA /* Release */ = {{ isa = XCBuildConfiguration; buildSettings = {{
    TARGETED_DEVICE_FAMILY = "{families}";
  }}; name = Release; }};
}}; }}
"""


def catalog(images):
    return json.dumps({"images": images, "info": {"version": 1, "author": "xcode"}})


class PlaceholderTests(unittest.TestCase):
    def setUp(self):
        temporary = tempfile.TemporaryDirectory(prefix="source-preflight-test-")
        self.addCleanup(temporary.cleanup)
        self.root = Path(temporary.name).resolve()
        self.icons = self.root / "ios/Runner/Assets.xcassets/AppIcon.appiconset"
        self.icons.mkdir(parents=True)
        self.sdk = self.root / "sdk"
        template = self.sdk / preflight.FLUTTER_ICON_TEMPLATES[1]
        template.mkdir(parents=True)
        (template / "Icon-App-1024x1024@1x.png").write_bytes(STOCK)
        (template / "Icon-App-empty@1x.png").write_bytes(b"")

    def declare(self, *names):
        (self.icons / "Contents.json").write_text(catalog(
            [{"idiom": "iphone", "size": "60x60", "scale": "2x", "filename": name}
             for name in names]))

    def test_the_sdk_template_convicts_a_shipped_stock_icon(self):
        (self.icons / "Icon-App-1024x1024@1x.png").write_bytes(STOCK)
        self.declare("Icon-App-1024x1024@1x.png")
        answer = preflight.inspect(self.root, self.sdk, only={"placeholder_icons"})
        self.assertFalse(answer["ok"])
        self.assertEqual(answer["failures"][0]["guideline"], "2.3.8")

    def test_a_stock_icon_the_catalog_does_not_reference_is_ignored(self):
        # ios-tv-remote keeps all fifteen stock files beside the eleven it ships.
        # Hashing the directory would convict an app whose icon is its own.
        (self.icons / "Icon-App-1024x1024@1x.png").write_bytes(STOCK)
        (self.icons / "1024.png").write_bytes(REAL)
        self.declare("1024.png")
        answer = preflight.inspect(self.root, self.sdk, only={"placeholder_icons"})
        self.assertTrue(answer["ok"], answer["failures"])

    def test_real_artwork_passes(self):
        (self.icons / "1024.png").write_bytes(REAL)
        self.declare("1024.png")
        answer = preflight.inspect(self.root, self.sdk, only={"placeholder_icons"})
        self.assertTrue(answer["ok"], answer["failures"])

    def test_an_empty_template_file_convicts_nothing(self):
        (self.icons / "blank.png").write_bytes(b"")
        self.declare("blank.png")
        answer = preflight.inspect(self.root, self.sdk, only={"placeholder_icons"})
        self.assertTrue(answer["ok"], answer["failures"])

    def test_the_refusal_names_the_unused_finished_artwork(self):
        (self.icons / "Icon-App-1024x1024@1x.png").write_bytes(STOCK)
        self.declare("Icon-App-1024x1024@1x.png")
        brand = self.root / "assets/brand"
        brand.mkdir(parents=True)
        (brand / "app-icon-1024.jpg").write_bytes(REAL)
        answer = preflight.inspect(self.root, self.sdk, only={"placeholder_icons"})
        self.assertIn("assets/brand/app-icon-1024.jpg", answer["failures"][0]["message"])

    def test_artwork_that_is_already_shipping_is_not_called_unused(self):
        (self.icons / "Icon-App-1024x1024@1x.png").write_bytes(STOCK)
        (self.icons / "real.png").write_bytes(REAL)
        self.declare("Icon-App-1024x1024@1x.png", "real.png")
        brand = self.root / "assets/brand"
        brand.mkdir(parents=True)
        (brand / "app-icon-1024.png").write_bytes(REAL)
        answer = preflight.inspect(self.root, self.sdk, only={"placeholder_icons"})
        self.assertNotIn("already in this repository", answer["failures"][0]["message"])

    def test_the_builtin_table_still_convicts_without_an_sdk(self):
        payload = b"synthetic stock artwork"
        known = hashlib.sha256(payload).hexdigest()
        preflight.KNOWN_PLACEHOLDERS[known] = "fixture stock icon"
        self.addCleanup(preflight.KNOWN_PLACEHOLDERS.pop, known, None)
        (self.icons / "Icon-App-1024x1024@1x.png").write_bytes(payload)
        self.declare("Icon-App-1024x1024@1x.png")
        answer = preflight.inspect(self.root, None, only={"placeholder_icons"})
        self.assertFalse(answer["ok"])
        self.assertIn("fixture stock icon", answer["failures"][0]["message"])

    def test_a_repository_with_no_icons_is_not_refused(self):
        answer = preflight.inspect(self.root, self.sdk, only={"placeholder_icons"})
        self.assertTrue(answer["ok"], answer["failures"])


class AndroidIconTests(unittest.TestCase):
    def setUp(self):
        temporary = tempfile.TemporaryDirectory(prefix="source-android-test-")
        self.addCleanup(temporary.cleanup)
        self.root = Path(temporary.name).resolve()
        self.res = self.root / "android/app/src/main/res/mipmap-xxxhdpi"
        self.res.mkdir(parents=True)
        self.manifest = self.root / "android/app/src/main/AndroidManifest.xml"

    def declare(self, name):
        self.manifest.write_text(
            f'<manifest><application android:icon="@mipmap/{name}"/></manifest>')

    def stock_digest(self):
        value = hashlib.sha256(STOCK).hexdigest()
        preflight.KNOWN_PLACEHOLDERS[value] = "flutter stock ic_launcher xxxhdpi"
        self.addCleanup(preflight.KNOWN_PLACEHOLDERS.pop, value, None)

    def test_the_declared_launcher_icon_is_judged(self):
        self.stock_digest()
        (self.res / "ic_launcher.png").write_bytes(STOCK)
        self.declare("ic_launcher")
        answer = preflight.inspect(self.root, None, only={"placeholder_icons"})
        self.assertFalse(answer["ok"])

    def test_a_leftover_stock_icon_the_manifest_ignores_is_not_a_finding(self):
        # Three of four Android hits in a fleet sweep were exactly this shape.
        self.stock_digest()
        (self.res / "ic_launcher.png").write_bytes(STOCK)
        (self.res / "launcher_icon.png").write_bytes(REAL)
        self.declare("launcher_icon")
        answer = preflight.inspect(self.root, None, only={"placeholder_icons"})
        self.assertTrue(answer["ok"], answer["failures"])


class CoverageTests(unittest.TestCase):
    def setUp(self):
        temporary = tempfile.TemporaryDirectory(prefix="source-coverage-test-")
        self.addCleanup(temporary.cleanup)
        self.root = Path(temporary.name).resolve()
        self.project = self.root / "ios/Runner.xcodeproj"
        self.project.mkdir(parents=True)
        self.icons = self.root / "ios/Runner/Assets.xcassets/AppIcon.appiconset"
        self.icons.mkdir(parents=True)

    def families(self, value: str):
        (self.project / "project.pbxproj").write_text(PBXPROJ.format(families=value))

    def write(self, images):
        (self.icons / "Contents.json").write_text(catalog(images))
        for image in images:
            if image.get("filename"):
                (self.icons / image["filename"]).write_bytes(png(64, 64))

    def iphone_only(self):
        return [{"idiom": "iphone", "size": "60x60", "scale": "2x", "filename": "a.png"},
                {"idiom": "iphone", "size": "60x60", "scale": "3x", "filename": "b.png"},
                {"idiom": "ios-marketing", "size": "1024x1024", "scale": "1x", "filename": "c.png"}]

    def test_ipad_target_without_ipad_entries_is_refused(self):
        self.families("1,2")
        self.write(self.iphone_only())
        answer = preflight.inspect(self.root, None, only={"icon_coverage"})
        self.assertFalse(answer["ok"])
        self.assertIn("76x76@2x", answer["failures"][0]["message"])
        self.assertIn("83.5x83.5@2x", answer["failures"][0]["message"])

    def test_iphone_only_target_is_not_refused(self):
        self.families("1")
        self.write(self.iphone_only())
        answer = preflight.inspect(self.root, None, only={"icon_coverage"})
        self.assertTrue(answer["ok"], answer["failures"])

    def test_a_universal_catalog_covers_ipad(self):
        self.families("1,2")
        self.write([{"idiom": "universal", "platform": "ios", "size": "1024x1024",
                     "scale": "1x", "filename": "u.png"}])
        answer = preflight.inspect(self.root, None, only={"icon_coverage"})
        self.assertTrue(answer["ok"], answer["failures"])

    def test_explicit_ipad_entries_pass(self):
        self.families("1,2")
        self.write(self.iphone_only() + [
            {"idiom": "ipad", "size": "76x76", "scale": "2x", "filename": "d.png"},
            {"idiom": "ipad", "size": "83.5x83.5", "scale": "2x", "filename": "e.png"}])
        answer = preflight.inspect(self.root, None, only={"icon_coverage"})
        self.assertTrue(answer["ok"], answer["failures"])

    def test_an_entry_with_no_filename_does_not_count_as_coverage(self):
        self.families("1,2")
        self.write(self.iphone_only() + [
            {"idiom": "ipad", "size": "76x76", "scale": "2x"},
            {"idiom": "ipad", "size": "83.5x83.5", "scale": "2x"}])
        answer = preflight.inspect(self.root, None, only={"icon_coverage"})
        self.assertFalse(answer["ok"])

    def test_vendored_and_build_copies_are_never_walked(self):
        self.families("1,2")
        self.write(self.iphone_only() + [
            {"idiom": "ipad", "size": "76x76", "scale": "2x", "filename": "d.png"},
            {"idiom": "ipad", "size": "83.5x83.5", "scale": "2x", "filename": "e.png"}])
        for vendored in ("Pods", "build", ".factory/cache/old"):
            stale = self.root / "ios" / vendored / "X/Assets.xcassets/AppIcon.appiconset"
            stale.mkdir(parents=True)
            (stale / "Contents.json").write_text(catalog(self.iphone_only()))
        answer = preflight.inspect(self.root, None, only={"icon_coverage"})
        self.assertTrue(answer["ok"], answer["failures"])


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