use insta_cmd::assert_cmd_snapshot;

use crate::common::TestContext;

#[test]
fn test_invalid_pytest_fixture_scope() {
    let context = TestContext::with_file(
        "test.py",
        r#"
                import pytest

                @pytest.fixture(scope="sessionss")
                def some_fixture() -> int:
                    return 1

                def test_all_scopes(
                    some_fixture: int,
                ) -> None:
                    assert some_fixture == 1
                "#,
    );

    assert_cmd_snapshot!(context.command(), @r#"
    success: false
    exit_code: 1
    ----- stdout -----
    test test::test_all_scopes ... FAILED

    diagnostics:

    error[invalid-fixture]: Discovered an invalid fixture `some_fixture`
     --> test.py:5:5
      |
    4 | @pytest.fixture(scope="sessionss")
    5 | def some_fixture() -> int:
      |     ^^^^^^^^^^^^
    6 |     return 1
      |
    info: 'FixtureFunctionDefinition' object is not an instance of 'FixtureFunctionDefinition'

    error[missing-fixtures]: Test `test_all_scopes` has missing fixtures
      --> test.py:8:5
       |
     6 |     return 1
     7 |
     8 | def test_all_scopes(
       |     ^^^^^^^^^^^^^^^
     9 |     some_fixture: int,
    10 | ) -> None:
       |
    info: Missing fixtures: `some_fixture`

    test result: FAILED. 0 passed; 1 failed; 0 skipped; finished in [TIME]

    ----- stderr -----
    "#);
}

#[test]
fn test_missing_fixture() {
    let context = TestContext::with_file(
        "test.py",
        r"
                def test_all_scopes(
                    missing_fixture: int,
                ) -> None:
                    assert missing_fixture == 1
                ",
    );

    assert_cmd_snapshot!(context.command(), @r"
    success: false
    exit_code: 1
    ----- stdout -----
    test test::test_all_scopes ... FAILED

    diagnostics:

    error[missing-fixtures]: Test `test_all_scopes` has missing fixtures
     --> test.py:2:5
      |
    2 | def test_all_scopes(
      |     ^^^^^^^^^^^^^^^
    3 |     missing_fixture: int,
    4 | ) -> None:
      |
    info: Missing fixtures: `missing_fixture`

    test result: FAILED. 0 passed; 1 failed; 0 skipped; finished in [TIME]

    ----- stderr -----
    ");
}

#[test]
fn test_fixture_fails_to_run() {
    let context = TestContext::with_file(
        "test.py",
        r"
                from karva import fixture

                @fixture
                def failing_fixture():
                    raise Exception('Fixture failed')

                def test_failing_fixture(failing_fixture):
                    pass
                ",
    );

    assert_cmd_snapshot!(context.command(), @r"
    success: false
    exit_code: 1
    ----- stdout -----
    test test::test_failing_fixture ... FAILED

    diagnostics:

    error[missing-fixtures]: Test `test_failing_fixture` has missing fixtures
     --> test.py:8:5
      |
    6 |     raise Exception('Fixture failed')
    7 |
    8 | def test_failing_fixture(failing_fixture):
      |     ^^^^^^^^^^^^^^^^^^^^
    9 |     pass
      |
    info: Missing fixtures: `failing_fixture`
    info: Fixture `failing_fixture` failed here
     --> test.py:6:5
      |
    4 | @fixture
    5 | def failing_fixture():
    6 |     raise Exception('Fixture failed')
      |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    7 |
    8 | def test_failing_fixture(failing_fixture):
      |
    info: Fixture failed

    test result: FAILED. 0 passed; 1 failed; 0 skipped; finished in [TIME]

    ----- stderr -----
    ");
}

#[test]
fn test_fixture_missing_fixtures() {
    let context = TestContext::with_file(
        "test.py",
        r"
                from karva import fixture

                @fixture
                def failing_fixture(missing_fixture):
                    return 1

                def test_failing_fixture(failing_fixture):
                    pass
                ",
    );

    assert_cmd_snapshot!(context.command(), @r"
    success: false
    exit_code: 1
    ----- stdout -----
    test test::test_failing_fixture ... FAILED

    diagnostics:

    error[missing-fixtures]: Test `test_failing_fixture` has missing fixtures
     --> test.py:8:5
      |
    6 |     return 1
    7 |
    8 | def test_failing_fixture(failing_fixture):
      |     ^^^^^^^^^^^^^^^^^^^^
    9 |     pass
      |
    info: Missing fixtures: `failing_fixture`
    info: failing_fixture() missing 1 required positional argument: 'missing_fixture'

    test result: FAILED. 0 passed; 1 failed; 0 skipped; finished in [TIME]

    ----- stderr -----
    ");
}

#[test]
fn missing_arguments_in_nested_function() {
    let context = TestContext::with_file(
        "test.py",
        r"
                def test_failing_fixture():

                    def inner(missing_fixture): ...

                    inner()
                   ",
    );

    assert_cmd_snapshot!(context.command(), @r"
    success: false
    exit_code: 1
    ----- stdout -----
    test test::test_failing_fixture ... FAILED

    diagnostics:

    error[test-failure]: Test `test_failing_fixture` failed
     --> test.py:2:5
      |
    2 | def test_failing_fixture():
      |     ^^^^^^^^^^^^^^^^^^^^
    3 |
    4 |     def inner(missing_fixture): ...
      |
    info: Test failed here
     --> test.py:6:5
      |
    4 |     def inner(missing_fixture): ...
    5 |
    6 |     inner()
      |     ^^^^^^^
      |
    info: test_failing_fixture.<locals>.inner() missing 1 required positional argument: 'missing_fixture'

    test result: FAILED. 0 passed; 1 failed; 0 skipped; finished in [TIME]

    ----- stderr -----
    ");
}

#[test]
fn test_failing_yield_fixture() {
    let context = TestContext::with_file(
        "test.py",
        r"
            import karva

            @karva.fixture
            def fixture():
                def foo():
                    raise ValueError('foo')
                yield foo()

            def test_failing_fixture(fixture):
                assert True
                   ",
    );

    assert_cmd_snapshot!(context.command(), @r"
    success: false
    exit_code: 1
    ----- stdout -----
    test test::test_failing_fixture ... FAILED

    diagnostics:

    error[missing-fixtures]: Test `test_failing_fixture` has missing fixtures
      --> test.py:10:5
       |
     8 |     yield foo()
     9 |
    10 | def test_failing_fixture(fixture):
       |     ^^^^^^^^^^^^^^^^^^^^
    11 |     assert True
       |
    info: Missing fixtures: `fixture`
    info: Fixture `fixture` failed here
     --> test.py:7:9
      |
    5 | def fixture():
    6 |     def foo():
    7 |         raise ValueError('foo')
      |         ^^^^^^^^^^^^^^^^^^^^^^^
    8 |     yield foo()
      |
    info: foo

    test result: FAILED. 0 passed; 1 failed; 0 skipped; finished in [TIME]

    ----- stderr -----
    ");
}

#[test]
fn test_fixture_generator_two_yields() {
    let context = TestContext::with_file(
        "test.py",
        r"
                import karva

                @karva.fixture
                def fixture_generator():
                    yield 1
                    yield 2

                def test_fixture_generator(fixture_generator):
                    assert fixture_generator == 1
                ",
    );

    assert_cmd_snapshot!(context.command(), @r"
    success: true
    exit_code: 0
    ----- stdout -----
    test test::test_fixture_generator(fixture_generator=1) ... ok

    diagnostics:

    warning[invalid-fixture-finalizer]: Discovered an invalid fixture finalizer `fixture_generator`
     --> test.py:5:5
      |
    4 | @karva.fixture
    5 | def fixture_generator():
      |     ^^^^^^^^^^^^^^^^^
    6 |     yield 1
    7 |     yield 2
      |
    info: Fixture had more than one yield statement

    test result: ok. 1 passed; 0 failed; 0 skipped; finished in [TIME]

    ----- stderr -----
    ");
}

#[test]
fn test_fixture_generator_fail_in_teardown() {
    let context = TestContext::with_file(
        "test.py",
        r#"
                import karva

                @karva.fixture
                def fixture_generator():
                    yield 1
                    raise ValueError("fixture-error")

                def test_fixture_generator(fixture_generator):
                    assert fixture_generator == 1
                "#,
    );

    assert_cmd_snapshot!(context.command(), @r#"
    success: true
    exit_code: 0
    ----- stdout -----
    test test::test_fixture_generator(fixture_generator=1) ... ok

    diagnostics:

    warning[invalid-fixture-finalizer]: Discovered an invalid fixture finalizer `fixture_generator`
     --> test.py:5:5
      |
    4 | @karva.fixture
    5 | def fixture_generator():
      |     ^^^^^^^^^^^^^^^^^
    6 |     yield 1
    7 |     raise ValueError("fixture-error")
      |
    info: Failed to reset fixture: fixture-error

    test result: ok. 1 passed; 0 failed; 0 skipped; finished in [TIME]

    ----- stderr -----
    "#);
}

#[test]
fn test_fixture_dependency_chain_failure() {
    let context = TestContext::with_file(
        "test.py",
        r"
                from karva import fixture

                @fixture
                def config():
                    raise Exception('config failed')

                @fixture
                def connection(config):
                    return config

                @fixture
                def db(connection):
                    return connection

                def test_with_db(db):
                    pass
                ",
    );

    assert_cmd_snapshot!(context.command(), @r"
    success: false
    exit_code: 1
    ----- stdout -----
    test test::test_with_db ... FAILED

    diagnostics:

    error[missing-fixtures]: Test `test_with_db` has missing fixtures
      --> test.py:16:5
       |
    14 |     return connection
    15 |
    16 | def test_with_db(db):
       |     ^^^^^^^^^^^^
    17 |     pass
       |
    info: Missing fixtures: `db`
    info: Fixture `db` requires `connection`
      --> test.py:13:5
       |
    12 | @fixture
    13 | def db(connection):
       |     ^^
    14 |     return connection
       |
    info: Fixture `connection` requires `config`
      --> test.py:9:5
       |
     8 | @fixture
     9 | def connection(config):
       |     ^^^^^^^^^^
    10 |     return config
       |
    info: Fixture `config` failed here
     --> test.py:6:5
      |
    4 | @fixture
    5 | def config():
    6 |     raise Exception('config failed')
      |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    7 |
    8 | @fixture
      |
    info: config failed

    test result: FAILED. 0 passed; 1 failed; 0 skipped; finished in [TIME]

    ----- stderr -----
    ");
}
