import {
    AzureCliCredential,
    AzureDeveloperCliCredential,
    ChainedTokenCredential,
    DeviceCodeCredential,
    InteractiveBrowserCredential,
    TokenCredential,
    VisualStudioCodeCredential,
    useIdentityPlugin,
} from "@azure/identity";
import * as dotenv from "dotenv";

dotenv.config();

const azureDevOpsScopes = ["https://app.vssps.visualstudio.com/.default"];
// Only pass a tenantId when one is explicitly set. Forcing `common` (the
// previous default) makes AzureCliCredential / VSCodeCredential fail and
// causes the chain to fall through to interactive auth, which pops the WAM
// account picker even when `az login` is valid.
const tenantId = process.env.AZURE_TENANT_ID || process.env.TENANT_ID;
// Background MCP processes have no UI; default to non-interactive only.
const allowInteractive = process.env.MCP_ALLOW_INTERACTIVE_AUTH === "1";

// MCP servers communicate over stdout (JSON-RPC). All diagnostics MUST go to
// stderr or they will corrupt the protocol stream.
const log = (...args: unknown[]) => console.error("[azure-devops-mcp]", ...args);

let cachedToken: string | null = null;
let tokenExpiresAt: number = 0;
let authenticationPromise: Promise<string> | null = null;
let credentialChain: TokenCredential | null = null;

// Lazily build a platform-aware credential chain:
//   1. AzureCliCredential          (`az login`)               - all platforms
//   2. AzureDeveloperCliCredential (`azd auth login`)         - all platforms
//   3. VisualStudioCodeCredential  (VS Code Azure sign-in)    - all platforms
//   4. InteractiveBrowserCredential + WAM broker              - Windows only
//   5. DeviceCodeCredential        (prints code to stderr)    - all platforms
function buildCredential(): TokenCredential {
    if (credentialChain) {
        return credentialChain;
    }

    const tenantOpts = tenantId
        ? { tenantId, additionallyAllowedTenants: ["*"] }
        : { additionallyAllowedTenants: ["*"] };

    const credentials: TokenCredential[] = [
        new AzureCliCredential(tenantOpts),
        new AzureDeveloperCliCredential(tenantOpts),
        new VisualStudioCodeCredential(tenantOpts),
    ];

    if (allowInteractive && process.platform === "win32") {
        try {
            // Load the WAM broker only on Windows; the native binding does not
            // exist for macOS/Linux and requiring it there throws.
            // eslint-disable-next-line @typescript-eslint/no-var-requires
            const { nativeBrokerPlugin } = require("@azure/identity-broker");
            useIdentityPlugin(nativeBrokerPlugin);
            credentials.push(
                new InteractiveBrowserCredential({
                    additionallyAllowedTenants: ["*"],
                    ...(tenantId ? { tenantId } : {}),
                    brokerOptions: {
                        enabled: true,
                        parentWindowHandle: new Uint8Array(0),
                        useDefaultBrokerAccount: false,
                        legacyEnableMsaPassthrough: true,
                    },
                } as any),
            );
        } catch (err) {
            log("WAM broker unavailable, skipping:", (err as Error).message);
        }
    }

    // Last-resort interactive flow that works in any terminal.
    if (allowInteractive) {
        credentials.push(
            new DeviceCodeCredential({
                ...(tenantId ? { tenantId } : {}),
                additionallyAllowedTenants: ["*"],
                userPromptCallback: (info) => {
                    log(`To sign in, open ${info.verificationUri} and enter code ${info.userCode}`);
                },
            }),
        );
    }

    credentialChain = new ChainedTokenCredential(...credentials);
    return credentialChain;
}

export async function getAccessToken(): Promise<string> {
    const now = Date.now();
    if (cachedToken && tokenExpiresAt > now) {
        return cachedToken;
    }

    if (authenticationPromise) {
        log("Authentication already in progress, waiting...");
        return authenticationPromise;
    }

    authenticationPromise = (async () => {
        try {
            const credential = buildCredential();
            const tokenResponse = await credential.getToken(azureDevOpsScopes.join(" "), {
                tenantId,
            });

            if (!tokenResponse || !tokenResponse.token) {
                throw new Error("Failed to acquire Azure DevOps token");
            }

            cachedToken = tokenResponse.token;
            tokenExpiresAt = tokenResponse.expiresOnTimestamp - 5 * 60 * 1000;
            return cachedToken;
        } catch (error) {
            log("Error acquiring token:", (error as Error).message ?? error);
            throw new Error(
                "Failed to acquire Azure DevOps access token. Try `az login` (recommended), `azd auth login`, or sign in to the Azure VS Code extension.",
            );
        } finally {
            authenticationPromise = null;
        }
    })();

    return authenticationPromise;
}