"""Integration tests for CLI locations commands.

This module tests locations command workflows including list/show/search
operations, filter options, staleness warnings, and output format validation
(table/JSON).
"""

from __future__ import annotations

import json
from unittest.mock import MagicMock

import pytest
from typer.testing import CliRunner

from teams_phone.cli.main import app
from teams_phone.exceptions import NotFoundError

from tests.integration.cli.conftest import make_location


class TestLocationsListIntegration:
    """Integration tests for locations list command."""

    @pytest.mark.integration
    def test_list_all_locations_table_output(
        self,
        runner: CliRunner,
        mock_cli_services_locations: dict[str, MagicMock],
    ) -> None:
        """List without filters returns all locations in table format."""
        mocks = mock_cli_services_locations
        locations = [
            make_location(
                description="Seattle HQ", city="Seattle", state_or_province="WA"
            ),
            make_location(
                location_id="loc-22222222-2222-2222-2222-222222222222",
                description="Portland Office",
                city="Portland",
                state_or_province="OR",
                is_default=False,
            ),
        ]
        mocks["location_service"].list_locations.return_value = locations
        mocks["location_service"].get_staleness_warning.return_value = None

        result = runner.invoke(app, ["--no-color", "locations", "list"])
        assert result.exit_code == 0
        assert "Seattle HQ" in result.stdout
        assert "Portland Office" in result.stdout
        assert "Seattle" in result.stdout
        assert "Portland" in result.stdout

    @pytest.mark.integration
    def test_list_locations_json_output(
        self,
        runner: CliRunner,
        mock_cli_services_locations: dict[str, MagicMock],
    ) -> None:
        """List locations with --json produces valid JSON array."""
        mocks = mock_cli_services_locations
        locations = [
            make_location(
                location_id="loc-12345",
                description="Seattle HQ",
                city="Seattle",
                address="123 Main St",
            ),
        ]
        mocks["location_service"].list_locations.return_value = locations
        mocks["location_service"].get_staleness_warning.return_value = None

        result = runner.invoke(app, ["--json", "locations", "list"])
        assert result.exit_code == 0

        data = json.loads(result.stdout)
        assert isinstance(data, list)
        assert len(data) == 1
        assert data[0]["location_id"] == "loc-12345"
        assert data[0]["description"] == "Seattle HQ"
        assert data[0]["city"] == "Seattle"
        assert data[0]["address"] == "123 Main St"

    @pytest.mark.integration
    def test_list_locations_city_filter(
        self,
        runner: CliRunner,
        mock_cli_services_locations: dict[str, MagicMock],
    ) -> None:
        """--city filter returns only locations in specified city."""
        mocks = mock_cli_services_locations
        locations = [
            make_location(description="Seattle HQ", city="Seattle"),
            make_location(
                location_id="loc-22222222-2222-2222-2222-222222222222",
                description="Portland Office",
                city="Portland",
            ),
        ]
        mocks["location_service"].list_locations.return_value = locations
        mocks["location_service"].get_staleness_warning.return_value = None

        result = runner.invoke(
            app, ["--no-color", "locations", "list", "--city", "Seattle"]
        )
        assert result.exit_code == 0
        assert "Seattle HQ" in result.stdout
        assert "Portland Office" not in result.stdout

    @pytest.mark.integration
    def test_list_locations_limit_option(
        self,
        runner: CliRunner,
        mock_cli_services_locations: dict[str, MagicMock],
    ) -> None:
        """--limit option restricts number of returned locations."""
        mocks = mock_cli_services_locations
        locations = [
            make_location(location_id="loc-1", description="Location 1"),
            make_location(location_id="loc-2", description="Location 2"),
            make_location(location_id="loc-3", description="Location 3"),
        ]
        mocks["location_service"].list_locations.return_value = locations
        mocks["location_service"].get_staleness_warning.return_value = None

        result = runner.invoke(app, ["--no-color", "locations", "list", "--limit", "2"])
        assert result.exit_code == 0
        assert "Location 1" in result.stdout
        assert "Location 2" in result.stdout
        assert "Location 3" not in result.stdout

    @pytest.mark.integration
    def test_list_locations_limit_and_all_mutually_exclusive(
        self,
        runner: CliRunner,
        mock_cli_services_locations: dict[str, MagicMock],
    ) -> None:
        """--limit and --all together produces ValidationError exit code 5."""
        result = runner.invoke(
            app, ["--no-color", "locations", "list", "--limit", "10", "--all"]
        )
        assert result.exit_code == 5
        assert "Cannot specify both" in result.stdout

    @pytest.mark.integration
    def test_list_locations_staleness_warning(
        self,
        runner: CliRunner,
        mock_cli_services_locations: dict[str, MagicMock],
    ) -> None:
        """Staleness warning is displayed when cache is stale."""
        mocks = mock_cli_services_locations
        locations = [make_location(description="Seattle HQ")]
        mocks["location_service"].list_locations.return_value = locations
        mocks[
            "location_service"
        ].get_staleness_warning.return_value = (
            "Location cache is 30 days old. Run 'Export-Locations.ps1' to refresh."
        )

        result = runner.invoke(app, ["--no-color", "locations", "list"])
        assert result.exit_code == 0
        # Check for parts of the warning without the numeric "30" which gets ANSI-highlighted
        assert "days old" in result.stdout
        assert "Export-Locations.ps1" in result.stdout
        assert "Seattle HQ" in result.stdout

    @pytest.mark.integration
    def test_list_locations_empty_result(
        self,
        runner: CliRunner,
        mock_cli_services_locations: dict[str, MagicMock],
    ) -> None:
        """Empty result shows info message in table mode."""
        mocks = mock_cli_services_locations
        mocks["location_service"].list_locations.return_value = []
        mocks["location_service"].get_staleness_warning.return_value = None

        result = runner.invoke(app, ["--no-color", "locations", "list"])
        assert result.exit_code == 0
        assert "No locations found" in result.stdout


class TestLocationsShowIntegration:
    """Integration tests for locations show command."""

    @pytest.mark.integration
    def test_show_location_table_output(
        self,
        runner: CliRunner,
        mock_cli_services_locations: dict[str, MagicMock],
    ) -> None:
        """Show location displays detailed information."""
        mocks = mock_cli_services_locations
        location = make_location(
            location_id="loc-seattle-hq",
            civic_address_id="civic-addr-seattle",
            description="Seattle HQ",
            company_name="Contoso",
            address="Main Street",
            city="Seattle",
            state_or_province="WA",
            postal_code="WA-ZIP",
            country_or_region="US",
            is_default=True,
        )
        mocks["location_service"].validate_location.return_value = location
        mocks["location_service"].get_staleness_warning.return_value = None

        result = runner.invoke(app, ["--no-color", "locations", "show", "Seattle HQ"])
        assert result.exit_code == 0
        assert "Seattle HQ" in result.stdout
        assert "loc-seattle-hq" in result.stdout
        assert "Contoso" in result.stdout
        assert "Main Street" in result.stdout
        assert "Seattle" in result.stdout
        assert "WA" in result.stdout
        assert "WA-ZIP" in result.stdout

    @pytest.mark.integration
    def test_show_location_json_output(
        self,
        runner: CliRunner,
        mock_cli_services_locations: dict[str, MagicMock],
    ) -> None:
        """Show location with --json produces valid JSON object."""
        mocks = mock_cli_services_locations
        location = make_location(
            location_id="loc-12345",
            description="Seattle HQ",
            city="Seattle",
            is_default=True,
        )
        mocks["location_service"].validate_location.return_value = location
        mocks["location_service"].get_staleness_warning.return_value = None

        result = runner.invoke(app, ["--json", "locations", "show", "Seattle HQ"])
        assert result.exit_code == 0

        data = json.loads(result.stdout)
        assert data["location_id"] == "loc-12345"
        assert data["description"] == "Seattle HQ"
        assert data["city"] == "Seattle"
        assert data["is_default"] is True

    @pytest.mark.integration
    def test_show_location_not_found(
        self,
        runner: CliRunner,
        mock_cli_services_locations: dict[str, MagicMock],
    ) -> None:
        """NotFoundError results in exit code 4."""
        mocks = mock_cli_services_locations
        mocks["location_service"].validate_location.side_effect = NotFoundError(
            "Location 'nonexistent' not found",
            remediation="Verify the location ID or description is correct.",
        )
        mocks["location_service"].get_staleness_warning.return_value = None

        result = runner.invoke(app, ["locations", "show", "nonexistent"])
        assert result.exit_code == 4
        assert "not found" in result.stdout.lower()

    @pytest.mark.integration
    def test_show_location_staleness_warning(
        self,
        runner: CliRunner,
        mock_cli_services_locations: dict[str, MagicMock],
    ) -> None:
        """Staleness warning is displayed on show command."""
        mocks = mock_cli_services_locations
        location = make_location(description="Seattle HQ")
        mocks["location_service"].validate_location.return_value = location
        mocks[
            "location_service"
        ].get_staleness_warning.return_value = (
            "Location cache is stale. Run 'Export-Locations.ps1' to refresh."
        )

        result = runner.invoke(app, ["--no-color", "locations", "show", "Seattle HQ"])
        assert result.exit_code == 0
        assert "stale" in result.stdout.lower()
        assert "Seattle HQ" in result.stdout


class TestLocationsSearchIntegration:
    """Integration tests for locations search command."""

    @pytest.mark.integration
    def test_search_locations_table_output(
        self,
        runner: CliRunner,
        mock_cli_services_locations: dict[str, MagicMock],
    ) -> None:
        """Search returns matching locations in table format."""
        mocks = mock_cli_services_locations
        locations = [
            make_location(description="Seattle HQ", city="Seattle"),
            make_location(
                location_id="loc-22222222-2222-2222-2222-222222222222",
                description="Seattle Branch",
                city="Seattle",
            ),
        ]
        mocks["location_service"].list_locations.return_value = locations
        mocks["location_service"].get_staleness_warning.return_value = None

        result = runner.invoke(app, ["--no-color", "locations", "search", "Seattle"])
        assert result.exit_code == 0
        assert "Seattle HQ" in result.stdout
        assert "Seattle Branch" in result.stdout
        assert "Search Results" in result.stdout

    @pytest.mark.integration
    def test_search_locations_json_output(
        self,
        runner: CliRunner,
        mock_cli_services_locations: dict[str, MagicMock],
    ) -> None:
        """Search with --json produces valid JSON array."""
        mocks = mock_cli_services_locations
        locations = [
            make_location(description="Seattle HQ", city="Seattle"),
        ]
        mocks["location_service"].list_locations.return_value = locations
        mocks["location_service"].get_staleness_warning.return_value = None

        result = runner.invoke(app, ["--json", "locations", "search", "Seattle"])
        assert result.exit_code == 0

        data = json.loads(result.stdout)
        assert isinstance(data, list)
        assert len(data) == 1
        assert data[0]["description"] == "Seattle HQ"

    @pytest.mark.integration
    def test_search_locations_with_field_filter(
        self,
        runner: CliRunner,
        mock_cli_services_locations: dict[str, MagicMock],
    ) -> None:
        """--field filter restricts search to specific field."""
        mocks = mock_cli_services_locations
        locations = [
            make_location(
                description="Main Office",
                city="Seattle",
                address="123 Main Street",
            ),
            make_location(
                location_id="loc-22222222-2222-2222-2222-222222222222",
                description="Seattle HQ",
                city="Portland",
            ),
        ]
        mocks["location_service"].list_locations.return_value = locations
        mocks["location_service"].get_staleness_warning.return_value = None

        # Search by city field only - should find "Main Office" (city=Seattle)
        result = runner.invoke(
            app, ["--no-color", "locations", "search", "Seattle", "--field", "city"]
        )
        assert result.exit_code == 0
        assert "Main Office" in result.stdout
        # "Seattle HQ" has city=Portland, so it shouldn't match city search for "Seattle"
        assert "Seattle HQ" not in result.stdout

    @pytest.mark.integration
    def test_search_locations_by_name(
        self,
        runner: CliRunner,
        mock_cli_services_locations: dict[str, MagicMock],
    ) -> None:
        """--field name searches by description."""
        mocks = mock_cli_services_locations
        locations = [
            make_location(description="Seattle HQ", city="Seattle"),
            make_location(
                location_id="loc-22222222-2222-2222-2222-222222222222",
                description="Portland Office",
                city="Portland",
            ),
        ]
        mocks["location_service"].list_locations.return_value = locations
        mocks["location_service"].get_staleness_warning.return_value = None

        result = runner.invoke(
            app, ["--no-color", "locations", "search", "HQ", "--field", "name"]
        )
        assert result.exit_code == 0
        assert "Seattle HQ" in result.stdout
        assert "Portland Office" not in result.stdout

    @pytest.mark.integration
    def test_search_locations_with_limit(
        self,
        runner: CliRunner,
        mock_cli_services_locations: dict[str, MagicMock],
    ) -> None:
        """--limit option restricts number of results."""
        mocks = mock_cli_services_locations
        locations = [
            make_location(location_id="loc-1", description="Office 1", city="Seattle"),
            make_location(location_id="loc-2", description="Office 2", city="Seattle"),
            make_location(location_id="loc-3", description="Office 3", city="Seattle"),
        ]
        mocks["location_service"].list_locations.return_value = locations
        mocks["location_service"].get_staleness_warning.return_value = None

        result = runner.invoke(
            app, ["--no-color", "locations", "search", "Office", "--limit", "2"]
        )
        assert result.exit_code == 0
        assert "Office 1" in result.stdout
        assert "Office 2" in result.stdout
        assert "Office 3" not in result.stdout

    @pytest.mark.integration
    def test_search_locations_empty_result(
        self,
        runner: CliRunner,
        mock_cli_services_locations: dict[str, MagicMock],
    ) -> None:
        """Empty search result shows info message."""
        mocks = mock_cli_services_locations
        mocks["location_service"].list_locations.return_value = []
        mocks["location_service"].get_staleness_warning.return_value = None

        result = runner.invoke(
            app, ["--no-color", "locations", "search", "nonexistent"]
        )
        assert result.exit_code == 0
        assert "No locations found" in result.stdout

    @pytest.mark.integration
    def test_search_locations_by_company(
        self,
        runner: CliRunner,
        mock_cli_services_locations: dict[str, MagicMock],
    ) -> None:
        """--field company searches by company name."""
        mocks = mock_cli_services_locations
        locations = [
            make_location(description="Contoso HQ", company_name="Contoso"),
            make_location(
                location_id="loc-22222222-2222-2222-2222-222222222222",
                description="Fabrikam Office",
                company_name="Fabrikam",
            ),
        ]
        mocks["location_service"].list_locations.return_value = locations
        mocks["location_service"].get_staleness_warning.return_value = None

        result = runner.invoke(
            app, ["--no-color", "locations", "search", "Contoso", "--field", "company"]
        )
        assert result.exit_code == 0
        assert "Contoso HQ" in result.stdout
        assert "Fabrikam Office" not in result.stdout

    @pytest.mark.integration
    def test_search_locations_staleness_warning(
        self,
        runner: CliRunner,
        mock_cli_services_locations: dict[str, MagicMock],
    ) -> None:
        """Staleness warning is displayed on search command."""
        mocks = mock_cli_services_locations
        locations = [make_location(description="Seattle HQ")]
        mocks["location_service"].list_locations.return_value = locations
        mocks[
            "location_service"
        ].get_staleness_warning.return_value = "Location cache is stale."

        result = runner.invoke(app, ["--no-color", "locations", "search", "Seattle"])
        assert result.exit_code == 0
        assert "stale" in result.stdout.lower()
