from __future__ import annotations

import json
import sys
import threading
import urllib.request
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
for path in (ROOT, Path(__file__).resolve().parent):
    if str(path) not in sys.path:
        sys.path.insert(0, str(path))

from Analyze8780.analyze8780_repository import AnalyzeRepository
from Analyze8780.analyze8780_main import build_server
from Shared.storage.sort_storage import connect_database, record_downloaded_file


def request_json(url: str, *, method: str = "GET", payload: dict | None = None) -> dict:
    body = json.dumps(payload).encode("utf-8") if payload is not None else None
    req = urllib.request.Request(url, data=body, method=method)
    if body is not None:
        req.add_header("Content-Type", "application/json")
    with urllib.request.urlopen(req, timeout=5) as response:
        return json.loads(response.read().decode("utf-8"))


def test_repository_and_http_api(tmp_path: Path) -> None:
    st_root = tmp_path / "_St"
    st_root.mkdir()
    db_path = st_root / "Shared" / "data" / "sort_local.sqlite3"
    repo = AnalyzeRepository(st_root, db_path)
    a = repo.save_observation({
        "username": "alpha", "shortcode": "A1", "views": 100, "likes": 10,
        "post_url": "https://www.instagram.com/explore/",
        "video_url": "https://cdn.example/A1.mp4",
        "observed_at": "2026-07-19T09:02:00Z",
    })
    repo.save_observation({
        "username": "alpha", "shortcode": "A1", "views": 160, "likes": 14,
        "observed_at": "2026-07-19T09:12:00Z",
    })
    assert repo.accounts()["total"] == 1
    assert repo.account_posts(a["account_id"], sort_by="views_delta")["items"][0]["views_delta"] == 60
    media_dir = tmp_path / "_StDown" / "instagram" / "alpha"
    media_dir.mkdir(parents=True)
    media_file = media_dir / "179_original.mp4"
    media_file.write_bytes(b"video")
    record_downloaded_file(
        st_root=st_root, file_path=media_file, source_mode="MANUAL_SINGLE",
        shortcode="A1", owner_username="alpha", asset_kind="VIDEO",
        account_path="alpha", event_uuid="analyze-original-file",
        file_size=media_file.stat().st_size,
    )
    image_file = media_dir / "179_original.jpg"
    image_file.write_bytes(b"image")
    record_downloaded_file(
        st_root=st_root, file_path=image_file, source_mode="MANUAL_SINGLE",
        shortcode="A1", owner_username="alpha", asset_kind="IMAGE",
        account_path="alpha", event_uuid="analyze-original-image",
        post_url="https://www.instagram.com/reel/A1/",
        video_url="https://cdn.example/A1.mp4",
        file_size=image_file.stat().st_size,
    )

    server = build_server(st_root, db_path, "127.0.0.1", 0)
    thread = threading.Thread(target=server.serve_forever, daemon=True)
    thread.start()
    host, port = server.server_address
    base = f"http://{host}:{port}"
    try:
        health = request_json(base + "/api/health")
        assert health["ok"] is True and health["service"] == "Analyze8780"
        assert health["version"] == "2.3.8.273"
        accounts = request_json(base + "/api/accounts?page=1&page_size=50")
        assert accounts["total"] == 1
        queue_saved = request_json(base + "/api/collection-accounts", method="PUT", payload={
            "items": [
                {"username": "alpha", "position": 1},
                {"username": "https://www.instagram.com/explore/", "position": 2},
                {"username": "beta", "position": 3},
            ]
        })
        assert queue_saved["ok"] is True and queue_saved["total"] == 2
        queue_loaded = request_json(base + "/api/collection-accounts")
        assert [item["username"] for item in queue_loaded["items"]] == ["alpha", "beta"]

        batch = request_json(base + "/api/observations/batch", method="POST", payload={
            "items": [{
                "username": "beta", "shortcode": "B1", "views": 0, "views_available": 0, "likes": 25,
                "comments": 8, "reposts": 4, "share_count": 999,
                "sort_by": "comments", "sort_value": 8, "source_kind": "LOCAL",
                "observed_at": "2026-07-25T10:07:00Z",
            }]
        })
        assert batch["ok"] is True and batch["saved_count"] == 1 and batch["skipped_count"] == 0 and batch["failed_count"] == 0
        unchanged = request_json(base + "/api/observations/batch", method="POST", payload={
            "items": [{
                "username": "beta", "shortcode": "B1", "views": 0, "views_available": 0, "likes": 25,
                "comments": 8, "reposts": 4, "sort_by": "likes", "sort_value": 25, "source_kind": "LOCAL",
                "observed_at": "2026-07-25T10:17:00Z",
            }]
        })
        assert unchanged["ok"] is True and unchanged["saved_count"] == 0 and unchanged["skipped_count"] == 1
        with connect_database(db_path) as conn:
            counts = conn.execute("""
                SELECT
                    (SELECT COUNT(*) FROM post_stat_history h JOIN posts p ON p.id=h.post_id WHERE p.shortcode='B1') AS history_count,
                    (SELECT COUNT(*) FROM post_stat_10m b JOIN posts p ON p.id=b.post_id WHERE p.shortcode='B1') AS bucket_count
            """).fetchone()
            assert counts["history_count"] == 1
            assert counts["bucket_count"] == 1
        changed = request_json(base + "/api/observations/batch", method="POST", payload={
            "items": [{
                "username": "beta", "shortcode": "B1", "views": 0, "views_available": 0, "likes": 26,
                "comments": 8, "reposts": 4, "sort_by": "likes", "sort_value": 26, "source_kind": "LOCAL",
                "observed_at": "2026-07-25T10:27:00Z",
            }]
        })
        assert changed["ok"] is True and changed["saved_count"] == 1 and changed["skipped_count"] == 0
        with connect_database(db_path) as conn:
            row = conn.execute("""
                SELECT s.reposts, s.views_available, s.sort_by, s.sort_value, s.source_kind, b.bucket_at
                FROM posts p
                JOIN post_stat_latest s ON s.post_id=p.id
                JOIN post_stat_10m b ON b.post_id=p.id
                WHERE p.shortcode='B1'
            """).fetchone()
            assert row["reposts"] == 4
            assert row["views_available"] == 0
            assert row["sort_by"] == "likes" and row["sort_value"] == 26 and row["source_kind"] == "LOCAL"
            assert row["bucket_at"] == "2026-07-25T10:20:00Z"
            counts = conn.execute("""
                SELECT
                    (SELECT COUNT(*) FROM post_stat_history h JOIN posts p ON p.id=h.post_id WHERE p.shortcode='B1') AS history_count,
                    (SELECT COUNT(*) FROM post_stat_10m b JOIN posts p ON p.id=b.post_id WHERE p.shortcode='B1') AS bucket_count
            """).fetchone()
            assert counts["history_count"] == 2
            assert counts["bucket_count"] == 2

        posts = request_json(base + f"/api/accounts/{a['account_id']}/posts?sort_by=views_delta")
        assert posts["items"][0]["views_per_hour"] == 360
        assert posts["items"][0]["post_url"] == "https://www.instagram.com/reel/A1/"
        assert posts["items"][0]["folder_name"] == "alpha"
        assert posts["items"][0]["file_name"] == "179_original.mp4"
        assert posts["items"][0]["has_mp4"] == 1
        assert posts["items"][0]["has_jpg"] == 1

        accounts_after = request_json(base + "/api/accounts?page=1&page_size=50")
        beta = next(item for item in accounts_after["items"] if item["username"] == "beta")
        beta_posts = request_json(base + f"/api/accounts/{beta['id']}/posts?sort_by=likes")
        assert beta_posts["total"] == 1
        assert beta_posts["items"][0]["shortcode"] == "B1"
        assert beta_posts["items"][0]["likes"] == 26
        assert beta_posts["items"][0]["has_mp4"] == 0
        assert beta_posts["items"][0]["has_jpg"] == 0
        deleted = request_json(base + f"/api/accounts/{a['account_id']}", method="DELETE")
        assert deleted["deleted"] is True
        tombstones = request_json(base + "/api/deleted/accounts")
        assert tombstones["items"][0]["username"] == "alpha"
    finally:
        server.shutdown()
        server.server_close()
        thread.join(timeout=5)


def test_runner_uses_py312() -> None:
    runner = (Path(__file__).resolve().parent / "run_Analyze8780.bat").read_text(encoding="utf-8")
    assert "call py312 analyze8780_main.py --host 127.0.0.1 --port 8780" in runner


def test_web_shows_saved_folder_and_original_filename() -> None:
    html = (Path(__file__).resolve().parent / "web" / "index.html").read_text(encoding="utf-8")
    assert "저장 폴더" in html
    assert "원본 파일" in html
    assert "p.folder_name" in html and "p.file_name" in html

def test_web_compact_account_sidebar_and_direct_selection() -> None:
    html = (Path(__file__).resolve().parent / "web" / "index.html").read_text(encoding="utf-8")
    assert "grid-template-columns:minmax(190px,230px)" in html
    assert 'id="accountSearch"' in html
    assert "page_size=200" in html
    config = json.loads((Path(__file__).resolve().parent / "analyze8780_config.json").read_text(encoding="utf-8"))
    assert config["page_size"] == 200
    assert "account-row" in html and "account-summary" in html
    assert "selectedAccountData" in html and "renderAccountDetail" in html
    assert "new URLSearchParams(location.search).get('account')" in html
    assert "history.replaceState" in html
    assert "좌측 계정을 클릭하세요." in html
    assert "🗑️" in html and "actual MP4/JPG" not in html




def test_web_download_badge_link_rel_and_image_ratio() -> None:
    html = (Path(__file__).resolve().parent / "web" / "index.html").read_text(encoding="utf-8")
    assert 'target="_blank" rel="noopener noreferrer"' in html
    assert 'target="_blank" rel="noopener"' not in html
    assert "downloadBadge" in html
    assert "kinds.push('JPG')" in html and "kinds.push('MP4')" in html
    assert "kinds.join(' · ')" in html
    assert ".card-media{position:relative;width:100%;aspect-ratio:16/9" in html
    assert ".card-media img{display:block;width:100%;height:100%;object-fit:cover}" in html
    assert ".card img{width:100%;aspect-ratio:1/1" not in html
    assert "j.items.forEach" in html


def test_database_file_management_http_api(tmp_path: Path) -> None:
    st_root = tmp_path / "_St"
    st_root.mkdir()
    server = build_server(st_root, None, "127.0.0.1", 0)
    thread = threading.Thread(target=server.serve_forever, daemon=True)
    thread.start()
    host, port = server.server_address
    base = f"http://{host}:{port}"
    try:
        saved = request_json(base + "/api/collection-accounts", method="PUT", payload={"items": [{"username": "alpha"}]})
        assert saved["db_filename"] == "sort_local.sqlite3"
        copied = request_json(base + "/api/database/save-as", method="POST", payload={"filename": "backup"})
        assert copied["action"] == "save_as" and copied["db_filename"] == "backup.sqlite3"
        assert [item["username"] for item in copied["items"]] == ["alpha"]
        fresh = request_json(base + "/api/database/new", method="POST", payload={"filename": "fresh"})
        assert fresh["db_filename"] == "fresh.sqlite3" and fresh["items"] == []
        opened = request_json(base + "/api/database/open", method="POST", payload={"filename": "backup.sqlite3"})
        assert opened["db_filename"] == "backup.sqlite3"
        assert [item["username"] for item in opened["items"]] == ["alpha"]
        files = request_json(base + "/api/database-files")
        assert files["active_filename"] == "backup.sqlite3"
        assert {"sort_local.sqlite3", "backup.sqlite3", "fresh.sqlite3"}.issubset(set(files["files"]))
    finally:
        server.shutdown()
        server.server_close()
        thread.join(timeout=5)
