import { Command } from 'commander';
import http from 'node:http';
import { loadConfig } from '../config/index.js';
import type { ConfigLoaderDeps } from '../config/index.js';
import { saveCredentials, loadCredentials, deleteCredentials } from '../auth/index.js';
import type { CredentialsDeps } from '../auth/index.js';

export interface PromptDeps {
  input(message: string): Promise<string>;
  password(message: string): Promise<string>;
}

export interface StatusResponse {
  hasUsers: boolean;
}

export interface AuthCommandDeps {
  configDeps: Partial<ConfigLoaderDeps>;
  credentialsDeps: Partial<CredentialsDeps>;
  prompt: PromptDeps;
  log: (...args: string[]) => void;
  loginRequest: (mcpdUrl: string, email: string, password: string) => Promise<LoginResponse>;
  logoutRequest: (mcpdUrl: string, token: string) => Promise<void>;
  statusRequest: (mcpdUrl: string) => Promise<StatusResponse>;
  bootstrapRequest: (mcpdUrl: string, email: string, password: string, name?: string) => Promise<LoginResponse>;
}

interface LoginResponse {
  token: string;
  user: { email: string };
}

function defaultLoginRequest(mcpdUrl: string, email: string, password: string): Promise<LoginResponse> {
  return new Promise((resolve, reject) => {
    const url = new URL('/api/v1/auth/login', mcpdUrl);
    const body = JSON.stringify({ email, password });
    const opts: http.RequestOptions = {
      hostname: url.hostname,
      port: url.port,
      path: url.pathname,
      method: 'POST',
      timeout: 10000,
      headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
    };
    const req = http.request(opts, (res) => {
      const chunks: Buffer[] = [];
      res.on('data', (chunk: Buffer) => chunks.push(chunk));
      res.on('end', () => {
        const raw = Buffer.concat(chunks).toString('utf-8');
        if (res.statusCode === 401) {
          reject(new Error('Invalid credentials'));
          return;
        }
        if ((res.statusCode ?? 0) >= 400) {
          reject(new Error(`Login failed (${res.statusCode}): ${raw}`));
          return;
        }
        resolve(JSON.parse(raw) as LoginResponse);
      });
    });
    req.on('error', (err) => reject(new Error(`Cannot reach mcpd: ${err.message}`)));
    req.on('timeout', () => { req.destroy(); reject(new Error('Login request timed out')); });
    req.write(body);
    req.end();
  });
}

function defaultLogoutRequest(mcpdUrl: string, token: string): Promise<void> {
  return new Promise((resolve) => {
    const url = new URL('/api/v1/auth/logout', mcpdUrl);
    const opts: http.RequestOptions = {
      hostname: url.hostname,
      port: url.port,
      path: url.pathname,
      method: 'POST',
      timeout: 10000,
      headers: { 'Authorization': `Bearer ${token}` },
    };
    const req = http.request(opts, (res) => {
      res.resume();
      res.on('end', () => resolve());
    });
    req.on('error', () => resolve()); // Don't fail logout on network errors
    req.on('timeout', () => { req.destroy(); resolve(); });
    req.end();
  });
}

function defaultStatusRequest(mcpdUrl: string): Promise<StatusResponse> {
  return new Promise((resolve, reject) => {
    const url = new URL('/api/v1/auth/status', mcpdUrl);
    const opts: http.RequestOptions = {
      hostname: url.hostname,
      port: url.port,
      path: url.pathname,
      method: 'GET',
      timeout: 10000,
      headers: { 'Content-Type': 'application/json' },
    };
    const req = http.request(opts, (res) => {
      const chunks: Buffer[] = [];
      res.on('data', (chunk: Buffer) => chunks.push(chunk));
      res.on('end', () => {
        const raw = Buffer.concat(chunks).toString('utf-8');
        if ((res.statusCode ?? 0) >= 400) {
          reject(new Error(`Status check failed (${res.statusCode}): ${raw}`));
          return;
        }
        resolve(JSON.parse(raw) as StatusResponse);
      });
    });
    req.on('error', (err) => reject(new Error(`Cannot reach mcpd: ${err.message}`)));
    req.on('timeout', () => { req.destroy(); reject(new Error('Status request timed out')); });
    req.end();
  });
}

function defaultBootstrapRequest(mcpdUrl: string, email: string, password: string, name?: string): Promise<LoginResponse> {
  return new Promise((resolve, reject) => {
    const url = new URL('/api/v1/auth/bootstrap', mcpdUrl);
    const payload: Record<string, string> = { email, password };
    if (name) {
      payload['name'] = name;
    }
    const body = JSON.stringify(payload);
    const opts: http.RequestOptions = {
      hostname: url.hostname,
      port: url.port,
      path: url.pathname,
      method: 'POST',
      timeout: 10000,
      headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
    };
    const req = http.request(opts, (res) => {
      const chunks: Buffer[] = [];
      res.on('data', (chunk: Buffer) => chunks.push(chunk));
      res.on('end', () => {
        const raw = Buffer.concat(chunks).toString('utf-8');
        if ((res.statusCode ?? 0) >= 400) {
          reject(new Error(`Bootstrap failed (${res.statusCode}): ${raw}`));
          return;
        }
        resolve(JSON.parse(raw) as LoginResponse);
      });
    });
    req.on('error', (err) => reject(new Error(`Cannot reach mcpd: ${err.message}`)));
    req.on('timeout', () => { req.destroy(); reject(new Error('Bootstrap request timed out')); });
    req.write(body);
    req.end();
  });
}

async function defaultInput(message: string): Promise<string> {
  const { default: inquirer } = await import('inquirer');
  const { answer } = await inquirer.prompt([{ type: 'input', name: 'answer', message }]);
  return answer as string;
}

async function defaultPassword(message: string): Promise<string> {
  const { default: inquirer } = await import('inquirer');
  const { answer } = await inquirer.prompt([{ type: 'password', name: 'answer', message }]);
  return answer as string;
}

const defaultDeps: AuthCommandDeps = {
  configDeps: {},
  credentialsDeps: {},
  prompt: { input: defaultInput, password: defaultPassword },
  log: (...args) => console.log(...args),
  loginRequest: defaultLoginRequest,
  logoutRequest: defaultLogoutRequest,
  statusRequest: defaultStatusRequest,
  bootstrapRequest: defaultBootstrapRequest,
};

export function createLoginCommand(deps?: Partial<AuthCommandDeps>): Command {
  const { configDeps, credentialsDeps, prompt, log, loginRequest, statusRequest, bootstrapRequest } = { ...defaultDeps, ...deps };

  return new Command('login')
    .description('Authenticate with mcpd')
    .option('--mcpd-url <url>', 'mcpd URL to authenticate against')
    .action(async (opts: { mcpdUrl?: string }) => {
      const config = loadConfig(configDeps);
      const mcpdUrl = opts.mcpdUrl ?? config.mcpdUrl;

      try {
        const status = await statusRequest(mcpdUrl);

        if (!status.hasUsers) {
          log('No users configured. Creating first admin account.');
          const email = await prompt.input('Email:');
          const password = await prompt.password('Password:');
          const name = await prompt.input('Name (optional):');

          const result = name
            ? await bootstrapRequest(mcpdUrl, email, password, name)
            : await bootstrapRequest(mcpdUrl, email, password);
          saveCredentials({
            token: result.token,
            mcpdUrl,
            user: result.user.email,
          }, credentialsDeps);
          log(`Logged in as ${result.user.email} (admin)`);
        } else {
          const email = await prompt.input('Email:');
          const password = await prompt.password('Password:');

          const result = await loginRequest(mcpdUrl, email, password);
          saveCredentials({
            token: result.token,
            mcpdUrl,
            user: result.user.email,
          }, credentialsDeps);
          log(`Logged in as ${result.user.email}`);
        }
      } catch (err) {
        log(`Login failed: ${(err as Error).message}`);
        process.exitCode = 1;
      }
    });
}

export function createLogoutCommand(deps?: Partial<AuthCommandDeps>): Command {
  const { credentialsDeps, log, logoutRequest } = { ...defaultDeps, ...deps };

  return new Command('logout')
    .description('Log out and remove stored credentials')
    .action(async () => {
      const creds = loadCredentials(credentialsDeps);
      if (!creds) {
        log('Not logged in');
        return;
      }

      await logoutRequest(creds.mcpdUrl, creds.token);
      deleteCredentials(credentialsDeps);
      log('Logged out successfully');
    });
}
