"""
TDD RED phase: test_preview_manager.py
PreviewManager 클래스에 대한 테스트 스위트
"""

import json
import os
import socket
import sys
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

# scripts 디렉토리를 path에 추가
sys.path.insert(0, str(Path(__file__).parent.parent))

from preview_manager import PreviewManager  # pyright: ignore[reportMissingImports]

# ────────────────────────────────────────────────────────
# Fixtures
# ────────────────────────────────────────────────────────


@pytest.fixture
def config_data():
    return {
        "port_assignments": {
            "insuwiki": 3001,
            "InfoKeyword": 3002,
            "ThreadAuto": 3003,
        },
        "next_port": 3004,
        "projects_dir": "/home/jay/projects",
        "tailscale_ip": "100.76.130.39",
    }


@pytest.fixture
def config_file(tmp_path, config_data):
    config_path = tmp_path / "preview-ports.json"
    config_path.write_text(json.dumps(config_data))
    return config_path


@pytest.fixture
def state_file(tmp_path):
    return tmp_path / "preview-state.json"


@pytest.fixture
def projects_dir(tmp_path):
    d = tmp_path / "projects"
    d.mkdir()
    return d


@pytest.fixture
def manager(config_file, state_file, projects_dir):
    return PreviewManager(
        config_path=str(config_file),
        state_path=str(state_file),
        projects_dir=str(projects_dir),
    )


# ────────────────────────────────────────────────────────
# MT-2-1: detect_project_type 테스트
# ────────────────────────────────────────────────────────


def test_detect_nextjs(manager, tmp_path):
    """package.json에 next가 있으면 nextjs 감지"""
    project_dir = tmp_path / "myapp"
    project_dir.mkdir()
    package_json = {
        "dependencies": {"next": "^14.0.0", "react": "^18.0.0"},
    }
    (project_dir / "package.json").write_text(json.dumps(package_json))

    result = manager.detect_project_type(str(project_dir))
    assert result == "nextjs"


def test_detect_vite(manager, tmp_path):
    """package.json에 vite가 있으면 vite 감지"""
    project_dir = tmp_path / "viteapp"
    project_dir.mkdir()
    package_json = {
        "devDependencies": {"vite": "^5.0.0"},
        "dependencies": {"react": "^18.0.0"},
    }
    (project_dir / "package.json").write_text(json.dumps(package_json))

    result = manager.detect_project_type(str(project_dir))
    assert result == "vite"


def test_detect_flask(manager, tmp_path):
    """requirements.txt 또는 app.py가 있으면 flask 감지"""
    # Case 1: requirements.txt에 flask가 있는 경우
    project_dir1 = tmp_path / "flaskapp1"
    project_dir1.mkdir()
    (project_dir1 / "requirements.txt").write_text("flask==2.3.0\ngunicorn==21.0.0\n")

    result1 = manager.detect_project_type(str(project_dir1))
    assert result1 == "flask"

    # Case 2: app.py가 있는 경우
    project_dir2 = tmp_path / "flaskapp2"
    project_dir2.mkdir()
    (project_dir2 / "app.py").write_text("from flask import Flask\napp = Flask(__name__)\n")

    result2 = manager.detect_project_type(str(project_dir2))
    assert result2 == "flask"


def test_detect_django(manager, tmp_path):
    """manage.py가 있으면 django 감지"""
    project_dir = tmp_path / "djangoapp"
    project_dir.mkdir()
    (project_dir / "manage.py").write_text("#!/usr/bin/env python\nimport django\n")

    result = manager.detect_project_type(str(project_dir))
    assert result == "django"


def test_detect_unknown(manager, tmp_path):
    """아무 파일도 없으면 None 반환"""
    project_dir = tmp_path / "emptyapp"
    project_dir.mkdir()

    result = manager.detect_project_type(str(project_dir))
    assert result is None


# ────────────────────────────────────────────────────────
# MT-2-6: 포트 할당 테스트
# ────────────────────────────────────────────────────────


def test_port_allocation(manager):
    """기본 프로젝트는 설정 포트, 신규는 3004부터 할당"""
    # 설정에 있는 프로젝트 → 설정 포트 반환
    assert manager.get_port("insuwiki") == 3001
    assert manager.get_port("InfoKeyword") == 3002
    assert manager.get_port("ThreadAuto") == 3003

    # 신규 프로젝트 → next_port(3004) 할당 후 next_port 증가
    new_port = manager.get_port("NewProject")
    assert new_port == 3004

    # 또 다른 신규 프로젝트 → 3005
    new_port2 = manager.get_port("AnotherProject")
    assert new_port2 == 3005


# ────────────────────────────────────────────────────────
# MT-2-7: 포트 충돌 감지 테스트
# ────────────────────────────────────────────────────────


def test_port_conflict(manager):
    """이미 사용 중인 포트 감지"""
    # 실제 서버가 없는 포트 → 사용 가능
    assert manager.check_port_available(19999) is True

    # socket mock으로 사용 중인 포트 시뮬레이션
    with patch("socket.socket") as mock_socket_class:
        mock_sock = MagicMock()
        mock_sock.connect_ex.return_value = 0  # 0 = 연결 성공(포트 사용 중)
        mock_socket_class.return_value.__enter__ = MagicMock(return_value=mock_sock)
        mock_socket_class.return_value.__exit__ = MagicMock(return_value=False)

        result = manager.check_port_available(3001)
        assert result is False


# ────────────────────────────────────────────────────────
# MT-2-8: URL 형식 검증 테스트
# ────────────────────────────────────────────────────────


def test_preview_url_format(manager):
    """URL 형식 검증: http://<tailscale_ip>:<port>/"""
    url = manager.get_preview_url(3001)
    assert url == "http://100.76.130.39:3001/"

    url2 = manager.get_preview_url(3004)
    assert url2 == "http://100.76.130.39:3004/"


# ────────────────────────────────────────────────────────
# MT-2-9: 설정 파일 로드 테스트
# ────────────────────────────────────────────────────────


def test_load_config(config_file, state_file, projects_dir, config_data):
    """설정 파일을 정상적으로 로드"""
    mgr = PreviewManager(
        config_path=str(config_file),
        state_path=str(state_file),
        projects_dir=str(projects_dir),
    )
    assert mgr.config["port_assignments"]["insuwiki"] == 3001
    assert mgr.config["next_port"] == 3004
    assert mgr.config["tailscale_ip"] == "100.76.130.39"


# ────────────────────────────────────────────────────────
# MT-2-10: 상태 파일 저장/로드 테스트
# ────────────────────────────────────────────────────────


def test_save_state(manager, state_file):
    """상태를 JSON 파일에 저장"""
    state = {
        "previews": {
            "insuwiki": {
                "port": 3001,
                "pid": 12345,
                "project_type": "nextjs",
                "url": "http://100.76.130.39:3001/",
                "started_at": "2026-03-09T10:00:00",
                "project_dir": "/home/jay/projects/insuwiki",
            }
        }
    }
    manager.save_state(state)

    assert state_file.exists()
    loaded = json.loads(state_file.read_text())
    assert loaded["previews"]["insuwiki"]["port"] == 3001
    assert loaded["previews"]["insuwiki"]["pid"] == 12345


def test_load_state(manager, state_file):
    """상태 파일 로드: 파일 없으면 빈 상태 반환"""
    # 파일 없는 경우
    result = manager.load_state()
    assert result == {"previews": {}}

    # 파일 있는 경우
    state = {
        "previews": {
            "InfoKeyword": {
                "port": 3002,
                "pid": 99999,
                "project_type": "vite",
                "url": "http://100.76.130.39:3002/",
                "started_at": "2026-03-09T11:00:00",
                "project_dir": "/home/jay/projects/InfoKeyword",
            }
        }
    }
    state_file.write_text(json.dumps(state))

    loaded = manager.load_state()
    assert loaded["previews"]["InfoKeyword"]["port"] == 3002
    assert loaded["previews"]["InfoKeyword"]["project_type"] == "vite"
