# 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.
from __future__ import annotations

import json
import subprocess
from unittest.mock import MagicMock, patch

import pytest

from oauth_draft.setup_creds import detect_from_address, main, parse_args

# --- detect_from_address ---------------------------------------------------


def test_detect_from_address_uses_env_var(monkeypatch):
    monkeypatch.setenv("GMAIL_FROM", "env@example.com")
    assert detect_from_address() == "env@example.com"


def test_detect_from_address_falls_back_to_git_config(monkeypatch):
    monkeypatch.delenv("GMAIL_FROM", raising=False)
    with patch(
        "oauth_draft.setup_creds.subprocess.check_output",
        return_value="git@example.com\n",
    ):
        assert detect_from_address() == "git@example.com"


def test_detect_from_address_returns_none_when_git_missing(monkeypatch):
    monkeypatch.delenv("GMAIL_FROM", raising=False)
    with patch(
        "oauth_draft.setup_creds.subprocess.check_output",
        side_effect=FileNotFoundError,
    ):
        assert detect_from_address() is None


def test_detect_from_address_returns_none_when_git_errors(monkeypatch):
    monkeypatch.delenv("GMAIL_FROM", raising=False)
    with patch(
        "oauth_draft.setup_creds.subprocess.check_output",
        side_effect=subprocess.CalledProcessError(1, "git"),
    ):
        assert detect_from_address() is None


def test_detect_from_address_returns_none_when_git_returns_empty(monkeypatch):
    monkeypatch.delenv("GMAIL_FROM", raising=False)
    with patch(
        "oauth_draft.setup_creds.subprocess.check_output",
        return_value="\n",
    ):
        assert detect_from_address() is None


# --- parse_args ------------------------------------------------------------


def test_parse_args_minimal(monkeypatch):
    # Avoid relying on the host's environment / git config: stub
    # detect_from_address out of the parser default.
    monkeypatch.setenv("GMAIL_FROM", "default@example.com")
    args = parse_args(["client.json"])
    assert args.client_secrets == "client.json"
    assert args.from_address == "default@example.com"
    assert args.out.endswith("/.config/apache-magpie/gmail-oauth.json")
    assert args.rm_client_secrets is False


def test_parse_args_overrides(monkeypatch):
    monkeypatch.setenv("GMAIL_FROM", "default@example.com")
    args = parse_args(
        [
            "client.json",
            "--from-address",
            "override@example.com",
            "--out",
            "/custom/path.json",
            "--rm-client-secrets",
        ]
    )
    assert args.from_address == "override@example.com"
    assert args.out == "/custom/path.json"
    assert args.rm_client_secrets is True


# --- main ------------------------------------------------------------------


def _client_secrets(tmp_path):
    p = tmp_path / "client_secrets.json"
    p.write_text(
        json.dumps(
            {
                "installed": {
                    "client_id": "cid",
                    "client_secret": "secret",
                }
            }
        )
    )
    return p


def _flow_creds(refresh_token="rt-1"):
    creds = MagicMock()
    creds.refresh_token = refresh_token
    creds.scopes = ["https://mail.google.com/"]
    return creds


def test_main_errors_when_no_from_address(tmp_path, monkeypatch):
    monkeypatch.delenv("GMAIL_FROM", raising=False)
    secrets = _client_secrets(tmp_path)
    out = tmp_path / "out.json"
    with patch(
        "oauth_draft.setup_creds.subprocess.check_output",
        side_effect=FileNotFoundError,
    ):
        with pytest.raises(SystemExit) as excinfo:
            main([str(secrets), "--out", str(out)])
    assert "Could not determine --from-address" in str(excinfo.value)


def test_main_errors_when_client_secrets_missing(tmp_path, monkeypatch):
    monkeypatch.setenv("GMAIL_FROM", "me@example.com")
    out = tmp_path / "out.json"
    with pytest.raises(SystemExit) as excinfo:
        main([str(tmp_path / "does-not-exist.json"), "--out", str(out)])
    assert "client_secrets not found" in str(excinfo.value)


def test_main_errors_when_flow_returns_no_refresh_token(tmp_path, monkeypatch):
    monkeypatch.setenv("GMAIL_FROM", "me@example.com")
    secrets = _client_secrets(tmp_path)
    out = tmp_path / "out.json"
    flow = MagicMock()
    flow.run_local_server.return_value = _flow_creds(refresh_token=None)
    with patch(
        "oauth_draft.setup_creds.InstalledAppFlow.from_client_secrets_file",
        return_value=flow,
    ):
        with pytest.raises(SystemExit) as excinfo:
            main([str(secrets), "--out", str(out)])
    assert "no refresh_token" in str(excinfo.value)


def test_main_writes_credentials_file(tmp_path, monkeypatch, capsys):
    monkeypatch.setenv("GMAIL_FROM", "me@example.com")
    secrets = _client_secrets(tmp_path)
    out_dir = tmp_path / "creds-dir"
    out = out_dir / "gmail-oauth.json"
    flow = MagicMock()
    flow.run_local_server.return_value = _flow_creds()
    with patch(
        "oauth_draft.setup_creds.InstalledAppFlow.from_client_secrets_file",
        return_value=flow,
    ):
        rc = main([str(secrets), "--out", str(out)])
    assert rc == 0
    written = json.loads(out.read_text())
    assert written == {
        "client_id": "cid",
        "client_secret": "secret",
        "refresh_token": "rt-1",
        "from_address": "me@example.com",
    }
    # Mode should be 600 (owner-rw only) per the atomic-write path.
    assert (out.stat().st_mode & 0o777) == 0o600
    # Parent dir should be 700.
    assert (out_dir.stat().st_mode & 0o777) == 0o700
    # Original client_secrets is left in place by default.
    assert secrets.exists()
    # The startup banner logs the path of the client_secrets file (NOT
    # its content). Asserts the variable rename
    # (`client_secrets` → `client_secrets_path`) didn't break the
    # log message that addresses CodeQL `py/clear-text-logging-
    # sensitive-data` finding #3 on PR #6.
    out_text = capsys.readouterr().out
    assert f"Running OAuth flow against {secrets.resolve()}" in out_text


def test_main_with_rm_client_secrets_deletes_input(tmp_path, monkeypatch, capsys):
    monkeypatch.setenv("GMAIL_FROM", "me@example.com")
    secrets = _client_secrets(tmp_path)
    out = tmp_path / "creds-dir" / "creds.json"
    flow = MagicMock()
    flow.run_local_server.return_value = _flow_creds()
    with patch(
        "oauth_draft.setup_creds.InstalledAppFlow.from_client_secrets_file",
        return_value=flow,
    ):
        main([str(secrets), "--out", str(out), "--rm-client-secrets"])
    assert not secrets.exists()
    # The "Removed <path>" log line — covers the second CodeQL
    # `py/clear-text-logging-sensitive-data` site on the renamed
    # `client_secrets_path` variable.
    out_text = capsys.readouterr().out
    assert f"Removed {secrets.resolve()}" in out_text


def test_main_handles_web_shaped_client_secrets(tmp_path, monkeypatch):
    monkeypatch.setenv("GMAIL_FROM", "me@example.com")
    secrets = tmp_path / "client.json"
    secrets.write_text(json.dumps({"web": {"client_id": "wcid", "client_secret": "wsecret"}}))
    out = tmp_path / "creds-dir" / "creds.json"
    flow = MagicMock()
    flow.run_local_server.return_value = _flow_creds()
    with patch(
        "oauth_draft.setup_creds.InstalledAppFlow.from_client_secrets_file",
        return_value=flow,
    ):
        main([str(secrets), "--out", str(out)])
    written = json.loads(out.read_text())
    assert written["client_id"] == "wcid"
    assert written["client_secret"] == "wsecret"
