# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
import os
import tempfile
from io import StringIO

import pytest

from otava.config import (
    NestedYAMLConfigFileParser,
    load_config_from_file,
)
from otava.main import create_otava_cli_parser
from otava.test_config import CsvTestConfig, GraphiteTestConfig, HistoStatTestConfig


def test_load_graphite_tests():
    config = load_config_from_file("tests/resources/sample_config.yaml")
    tests = config.tests
    assert len(tests) == 4
    test = tests["remote1"]
    assert isinstance(test, GraphiteTestConfig)
    assert len(test.metrics) == 7
    print(test.metrics)
    assert test.prefix == "performance_regressions.my_product.%{BRANCH}.test1"
    assert test.metrics["throughput"].name == "throughput"
    assert test.metrics["throughput"].suffix is not None
    assert test.metrics["p50"].name == "p50"
    assert test.metrics["p50"].direction == -1
    assert test.metrics["p50"].scale == 1.0e-6
    assert test.metrics["p50"].suffix is not None


def test_load_csv_tests():
    config = load_config_from_file("tests/resources/sample_config.yaml")
    tests = config.tests
    assert len(tests) == 4
    test = tests["local1"]
    assert isinstance(test, CsvTestConfig)
    assert len(test.metrics) == 2
    assert len(test.attributes) == 1
    assert test.file == "tests/resources/sample.csv"

    test = tests["local2"]
    assert isinstance(test, CsvTestConfig)
    assert len(test.metrics) == 2
    assert test.metrics["m1"].column == "metric1"
    assert test.metrics["m1"].direction == 1
    assert test.metrics["m2"].column == "metric2"
    assert test.metrics["m2"].direction == -1
    assert len(test.attributes) == 1
    assert test.file == "tests/resources/sample.csv"


def test_load_test_groups():
    config = load_config_from_file("tests/resources/sample_config.yaml")
    groups = config.test_groups
    assert len(groups) == 2
    assert len(groups["remote"]) == 2


def test_load_histostat_config():
    config = load_config_from_file("tests/resources/histostat_test_config.yaml")
    tests = config.tests
    assert len(tests) == 1
    test = tests["histostat-sample"]
    assert isinstance(test, HistoStatTestConfig)
    # 14 tags * 12 tag_metrics == 168 unique metrics
    assert len(test.fully_qualified_metric_names()) == 168


@pytest.mark.parametrize(
    "config_property",
    [
        # property, accessor, env_var, cli_flag, [config value, env value, cli value]
        ("slack_token", lambda c: c.slack.bot_token, "SLACK_BOT_TOKEN", "--slack-token"),
        ("bigquery_project_id", lambda c: c.bigquery.project_id, "BIGQUERY_PROJECT_ID", "--bigquery-project-id"),
        ("bigquery_dataset", lambda c: c.bigquery.dataset, "BIGQUERY_DATASET", "--bigquery-dataset"),
        ("bigquery_credentials", lambda c: c.bigquery.credentials, "BIGQUERY_VAULT_SECRET", "--bigquery-credentials"),
        ("grafana_url", lambda c: c.grafana.url, "GRAFANA_ADDRESS", "--grafana-url"),
        ("grafana_user", lambda c: c.grafana.user, "GRAFANA_USER", "--grafana-user"),
        ("grafana_password", lambda c: c.grafana.password, "GRAFANA_PASSWORD", "--grafana-password"),
        ("graphite_url", lambda c: c.graphite.url, "GRAPHITE_ADDRESS", "--graphite-url"),
        ("postgres_hostname", lambda c: c.postgres.hostname, "POSTGRES_HOSTNAME", "--postgres-hostname"),
        ("postgres_port", lambda c: c.postgres.port, "POSTGRES_PORT", "--postgres-port", 1111, 2222, 3333),
        ("postgres_username", lambda c: c.postgres.username, "POSTGRES_USERNAME", "--postgres-username"),
        ("postgres_password", lambda c: c.postgres.password, "POSTGRES_PASSWORD", "--postgres-password"),
        ("postgres_database", lambda c: c.postgres.database, "POSTGRES_DATABASE", "--postgres-database"),
    ],
    ids=lambda v: v[0],  # use the property name for the parameterized test name
)
def test_configuration_substitutions(config_property):
    config_file = "tests/resources/substitution_test_config.yaml"
    accessor = config_property[1]

    if len(config_property) == 4:
        config_value = f"config_{config_property[0]}"
        env_config_value = f"env_{config_property[0]}"
        cli_config_value = f"cli_{config_property[0]}"
    else:
        config_value = config_property[4]
        env_config_value = config_property[5]
        cli_config_value = config_property[6]

    # test value from config file
    config = load_config_from_file(config_file)
    assert accessor(config) == config_value

    # test env var overrides values from config file
    os.environ[config_property[2]] = str(env_config_value)
    try:
        config = load_config_from_file(config_file)
        assert accessor(config) == env_config_value
    finally:
        os.environ.pop(config_property[2])

    # test cli values override values from config file
    config = load_config_from_file(config_file, arg_overrides=[config_property[3], str(cli_config_value)])
    assert accessor(config) == cli_config_value

    # test cli values override values from config file and env var
    os.environ[config_property[2]] = str(env_config_value)
    try:
        config = load_config_from_file(config_file, arg_overrides=[config_property[3], str(cli_config_value)])
        assert accessor(config) == cli_config_value
    finally:
        os.environ.pop(config_property[2])


def test_config_section_yaml_parser_flattens_only_config_sections():
    """Test that NestedYAMLConfigFileParser only flattens the specified config sections."""

    parser = NestedYAMLConfigFileParser()
    test_yaml = """
graphite:
  url: http://example.com
  timeout: 30
slack:
  bot_token: test-token
  channel: "#alerts"
postgres:
  hostname: localhost
  port: 5432
templates:
    aggregate_mem:
      type: postgres
      time_column: commit_ts
      attributes: [experiment_id, config_id, commit]
      metrics:
        process_cumulative_rate_mean:
          direction: 1
          scale: 1
        process_cumulative_rate_stderr:
          direction: -1
          scale: 1
        process_cumulative_rate_diff:
          direction: -1
          scale: 1
      query: |
        SELECT e.commit,
               e.commit_ts,
               r.process_cumulative_rate_mean,
               r.process_cumulative_rate_stderr,
               r.process_cumulative_rate_diff,
               r.experiment_id,
               r.config_id
        FROM results r
        INNER JOIN configs c ON r.config_id = c.id
        INNER JOIN experiments e ON r.experiment_id = e.id
        WHERE e.exclude_from_analysis = false AND
              e.branch = 'trunk' AND
              e.username = 'ci' AND
              c.store = 'MEM' AND
              c.cache = true AND
              c.benchmark = 'aggregate' AND
              c.instance_type = 'ec2i3.large'
        ORDER BY e.commit_ts ASC;
"""

    stream = StringIO(test_yaml)
    result = parser.parse(stream)

    # Should flatten config sections
    expected_keys = {
        'graphite-url', 'graphite-timeout',
        'slack-bot-token', 'slack-channel',
        'postgres-hostname', 'postgres-port'
    }

    assert set(result.keys()) == expected_keys
    assert result['graphite-url'] == 'http://example.com'
    assert result['graphite-timeout'] == '30'
    assert result['slack-bot-token'] == 'test-token'
    assert result['slack-channel'] == '#alerts'
    assert result['postgres-hostname'] == 'localhost'
    assert result['postgres-port'] == '5432'

    # Should NOT contain any keys from ignored sections
    ignored_sections = {'templates', 'tests', 'test_groups'}
    for key in result.keys():
        section = key.split('-')[0]
        assert section not in ignored_sections, f"Found key '{key}' from ignored section '{section}'"


def test_cli_precedence_over_env_vars():
    """Test that CLI arguments take precedence over environment variables."""

    # Set up environment variables
    env_vars = {
        "POSTGRES_HOSTNAME": "env-host.com",
        "POSTGRES_PORT": "5433",
        "POSTGRES_DATABASE": "env_db",
        "SLACK_BOT_TOKEN": "env-slack-token",
    }

    for key, value in env_vars.items():
        os.environ[key] = value

    # Create a simple config file
    config_content = """
postgres:
  hostname: config_host
  port: 5432
  database: config_db
  username: config_user
  password: config_pass

slack:
  token: config_slack_token
"""

    try:
        with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
            f.write(config_content)
            config_file_path = f.name

        try:
            # Test 1: Only environment variables (no CLI overrides)
            config_env_only = load_config_from_file(config_file_path)

            # Environment variables should override config file values
            assert config_env_only.postgres.hostname == "env-host.com"
            assert config_env_only.postgres.port == 5433
            assert config_env_only.postgres.database == "env_db"
            assert config_env_only.slack.bot_token == "env-slack-token"

            # Values without env vars should use config file values
            assert config_env_only.postgres.username == "config_user"
            assert config_env_only.postgres.password == "config_pass"

            # Test 2: CLI arguments should override environment variables
            cli_overrides = [
                "--postgres-hostname",
                "cli-host.com",
                "--postgres-port",
                "5434",
                "--slack-token",
                "cli-slack-token",
            ]

            config_cli_override = load_config_from_file(
                config_file_path, arg_overrides=cli_overrides
            )

            # CLI overrides should win
            assert config_cli_override.postgres.hostname == "cli-host.com"
            assert config_cli_override.postgres.port == 5434
            assert config_cli_override.slack.bot_token == "cli-slack-token"

            # Values without CLI override should still use env vars
            assert config_cli_override.postgres.database == "env_db"
            assert config_cli_override.postgres.username == "config_user"
            assert config_cli_override.postgres.password == "config_pass"

        finally:
            os.unlink(config_file_path)

    finally:
        # Clean up environment variables
        for key in env_vars:
            if key in os.environ:
                del os.environ[key]


def test_unknown_argument_raises_error(capsys):
    """Test that unknown arguments raise an error."""
    parser = create_otava_cli_parser()

    # Unknown argument should cause SystemExit
    with pytest.raises(SystemExit) as exc_info:
        parser.parse_args(["--banana", "list-groups"])

    # argparse exits with code 2 for invalid arguments
    assert exc_info.value.code == 2

    # Check error message
    captured = capsys.readouterr()
    assert "unrecognized arguments: --banana" in captured.err
