from __future__ import annotations

import json
import os
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest

from tests.support.ffx_bundle import (
    SourceTrackSpec,
    build_controller_context,
    create_source_fixture,
    dispose_controller_context,
)

from ffx.pattern_controller import PatternController
from ffx.show_controller import ShowController
from ffx.show_descriptor import ShowDescriptor
from ffx.shifted_season_controller import ShiftedSeasonController
from ffx.track_codec import TrackCodec
from ffx.track_descriptor import TrackDescriptor
from ffx.track_type import TrackType

try:
    import pytest
except ImportError:  # pragma: no cover - unittest-only environments
    pytest = None

if pytest is not None:
    pytestmark = [pytest.mark.integration]


SRC_ROOT = Path(__file__).resolve().parents[2] / "src"


def run_ffx_unmux(workdir: Path, home_dir: Path, database_path: Path, *args: str) -> subprocess.CompletedProcess[str]:
    env = os.environ.copy()
    env["HOME"] = str(home_dir)
    existing_pythonpath = env.get("PYTHONPATH", "")
    env["PYTHONPATH"] = str(SRC_ROOT) if not existing_pythonpath else f"{SRC_ROOT}{os.pathsep}{existing_pythonpath}"

    command = [
        sys.executable,
        "-m",
        "ffx",
        "--database-file",
        str(database_path),
        "unmux",
        *args,
    ]
    return subprocess.run(command, cwd=workdir, env=env, capture_output=True, text=True)


class UnmuxCliTests(unittest.TestCase):
    def setUp(self):
        self.tempdir = tempfile.TemporaryDirectory()
        self.workdir = Path(self.tempdir.name)
        self.home_dir = self.workdir / "home"
        self.home_dir.mkdir()
        self.database_path = self.workdir / "test.db"

    def tearDown(self):
        self.tempdir.cleanup()

    def write_config(self, data: dict) -> None:
        config_dir = self.home_dir / ".local" / "etc"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "ffx.json").write_text(json.dumps(data), encoding="utf-8")

    def assertCompleted(self, completed):
        if completed.returncode != 0:
            self.fail(
                "FFX unmux failed\n"
                f"STDOUT:\n{completed.stdout}\n"
                f"STDERR:\n{completed.stderr}"
            )

    def seed_matching_show(self, pattern_expression: str, *, indicator_season_digits: int, indicator_episode_digits: int) -> None:
        context = build_controller_context(self.database_path)
        try:
            ShowController(context).updateShow(
                ShowDescriptor(
                    id=1,
                    name="Unmux Test Show",
                    year=2000,
                    indicator_season_digits=indicator_season_digits,
                    indicator_episode_digits=indicator_episode_digits,
                )
            )
            PatternController(context).savePatternSchema(
                {
                    "show_id": 1,
                    "pattern": pattern_expression,
                    "quality": 0,
                    "notes": "",
                },
                trackDescriptors=[
                    TrackDescriptor(
                        index=0,
                        source_index=0,
                        track_type=TrackType.VIDEO,
                        codec_name=TrackCodec.H264,
                        tags={},
                        disposition_set=set(),
                    )
                ],
            )
        finally:
            dispose_controller_context(context)

    def add_show_shift(
        self,
        *,
        show_id: int,
        original_season: int,
        first_episode: int,
        last_episode: int,
        season_offset: int,
        episode_offset: int,
    ) -> None:
        context = build_controller_context(self.database_path)
        try:
            ShiftedSeasonController(context).addShiftedSeason(
                showId=show_id,
                shiftedSeasonObj={
                    "original_season": original_season,
                    "first_episode": first_episode,
                    "last_episode": last_episode,
                    "season_offset": season_offset,
                    "episode_offset": episode_offset,
                },
            )
        finally:
            dispose_controller_context(context)

    def test_subtitles_only_without_output_directory_uses_configured_base_plus_label(self):
        self.write_config(
            {
                "subtitlesDirectory": "~/.local/var/sync/subtitles",
            }
        )
        source_filename = "unmux_s01e01.mkv"
        source_path = create_source_fixture(
            self.workdir,
            source_filename,
            [
                SourceTrackSpec(TrackType.VIDEO, identity="video-0"),
                SourceTrackSpec(
                    TrackType.SUBTITLE,
                    identity="subtitle-1",
                    language="eng",
                    subtitle_lines=("subtitle payload",),
                ),
            ],
        )

        completed = run_ffx_unmux(
            self.workdir,
            self.home_dir,
            self.database_path,
            "--subtitles-only",
            "--label",
            "dball",
            str(source_path),
        )
        self.assertCompleted(completed)

        expected_directory = self.home_dir / ".local" / "var" / "sync" / "subtitles" / "dball"
        self.assertTrue(expected_directory.is_dir(), expected_directory)

    def test_unmux_uses_configured_indicator_digits_in_output_filenames(self):
        self.write_config(
            {
                "defaultIndicatorSeasonDigits": 3,
                "defaultIndicatorEpisodeDigits": 4,
            }
        )
        source_filename = "unmux_s01e01.mkv"
        output_directory = self.workdir / "unmux-output"
        output_directory.mkdir()
        source_path = create_source_fixture(
            self.workdir,
            source_filename,
            [
                SourceTrackSpec(TrackType.VIDEO, identity="video-0"),
            ],
        )

        completed = run_ffx_unmux(
            self.workdir,
            self.home_dir,
            self.database_path,
            "--label",
            "dball",
            "--output-directory",
            str(output_directory),
            str(source_path),
        )
        self.assertCompleted(completed)

        output_filenames = sorted(path.name for path in output_directory.iterdir())
        self.assertEqual(1, len(output_filenames), output_filenames)
        self.assertTrue(
            output_filenames[0].startswith("dball_S001E0001_"),
            output_filenames,
        )

    def test_unmux_prefers_matched_show_indicator_digits_over_config_defaults(self):
        self.write_config(
            {
                "defaultIndicatorSeasonDigits": 4,
                "defaultIndicatorEpisodeDigits": 4,
            }
        )
        self.seed_matching_show(
            r"^unmux_([sS][0-9]+[eE][0-9]+)\.mkv$",
            indicator_season_digits=1,
            indicator_episode_digits=3,
        )
        source_filename = "unmux_s01e01.mkv"
        output_directory = self.workdir / "unmux-output"
        output_directory.mkdir()
        source_path = create_source_fixture(
            self.workdir,
            source_filename,
            [
                SourceTrackSpec(TrackType.VIDEO, identity="video-0"),
            ],
        )

        completed = run_ffx_unmux(
            self.workdir,
            self.home_dir,
            self.database_path,
            "--label",
            "dball",
            "--output-directory",
            str(output_directory),
            str(source_path),
        )
        self.assertCompleted(completed)

        output_filenames = sorted(path.name for path in output_directory.iterdir())
        self.assertEqual(1, len(output_filenames), output_filenames)
        self.assertTrue(
            output_filenames[0].startswith("dball_S1E001_"),
            output_filenames,
        )

    def test_unmux_applies_shifted_season_mapping_to_output_filenames(self):
        self.seed_matching_show(
            r"^unmux_([sS][0-9]+[eE][0-9]+)\.mkv$",
            indicator_season_digits=2,
            indicator_episode_digits=2,
        )
        self.add_show_shift(
            show_id=1,
            original_season=1,
            first_episode=1,
            last_episode=99,
            season_offset=1,
            episode_offset=-88,
        )
        source_filename = "unmux_s01e89.mkv"
        output_directory = self.workdir / "unmux-output"
        output_directory.mkdir()
        source_path = create_source_fixture(
            self.workdir,
            source_filename,
            [
                SourceTrackSpec(TrackType.VIDEO, identity="video-0"),
                SourceTrackSpec(
                    TrackType.SUBTITLE,
                    identity="subtitle-1",
                    language="eng",
                    subtitle_lines=("subtitle payload",),
                ),
            ],
        )

        completed = run_ffx_unmux(
            self.workdir,
            self.home_dir,
            self.database_path,
            "--label",
            "dball",
            "--output-directory",
            str(output_directory),
            "--subtitles-only",
            str(source_path),
        )
        self.assertCompleted(completed)

        self.assertIn(
            "Unmuxing stream 1 into file dball_S02E01_1_eng",
            completed.stderr,
        )


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