import * as plugins from './plugins.js';
import * as interfaces from './interfaces.js';

export const reducePathDescriptorToPath = async (pathDescriptorArg: interfaces.IPathDecriptor): Promise<string> => {
  let returnPath = ``
  if (pathDescriptorArg.directory) {
    if (pathDescriptorArg.path && plugins.path.isAbsolute(pathDescriptorArg.path)) {
      console.warn('Directory is being ignored when path is absolute.');
      returnPath = pathDescriptorArg.path;
    } else if (pathDescriptorArg.path) {
      returnPath = plugins.path.join(pathDescriptorArg.directory.getBasePath(), pathDescriptorArg.path);
    }
  } else if (pathDescriptorArg.path) {
    returnPath = pathDescriptorArg.path;
  } else {
    throw new Error('You must specify either a path or a directory.');
  }
  if (returnPath.startsWith('/')) {
    returnPath = returnPath.substring(1);
  }
  return returnPath;
}

// Storage Descriptor Normalization
export interface IStorageWarning {
  code: string;
  message: string;
}

export interface INormalizedStorageConfig {
  endpointUrl: string;
  host: string;
  protocol: 'http' | 'https';
  port?: number;
  region: string;
  credentials: {
    accessKeyId: string;
    secretAccessKey: string;
  };
  forcePathStyle: boolean;
}

function coerceBooleanMaybe(value: unknown): { value: boolean | undefined; warning?: IStorageWarning } {
  if (typeof value === 'boolean') return { value };
  if (typeof value === 'string') {
    const v = value.trim().toLowerCase();
    if (v === 'true' || v === '1') {
      return { 
        value: true, 
        warning: { 
          code: 'SBK_S3_COERCED_USESSL', 
          message: `Coerced useSsl='${value}' (string) to boolean true.` 
        } 
      };
    }
    if (v === 'false' || v === '0') {
      return { 
        value: false, 
        warning: { 
          code: 'SBK_S3_COERCED_USESSL', 
          message: `Coerced useSsl='${value}' (string) to boolean false.` 
        } 
      };
    }
  }
  return { value: undefined };
}

function coercePortMaybe(port: unknown): { value: number | undefined; warning?: IStorageWarning } {
  if (port === undefined || port === null || port === '') return { value: undefined };
  const n = typeof port === 'number' ? port : Number(String(port).trim());
  if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0 || n > 65535) {
    return { 
      value: undefined, 
      warning: { 
        code: 'SBK_S3_INVALID_PORT', 
        message: `Invalid port '${String(port)}' - expected integer in [1..65535].` 
      } 
    };
  }
  return { value: n };
}

function sanitizeEndpointString(raw: unknown): { value: string; warnings: IStorageWarning[] } {
  const warnings: IStorageWarning[] = [];
  let s = String(raw ?? '').trim();
  if (s !== String(raw ?? '')) {
    warnings.push({ 
      code: 'SBK_S3_TRIMMED_ENDPOINT', 
      message: 'Trimmed surrounding whitespace from endpoint.' 
    });
  }
  return { value: s, warnings };
}

function parseEndpointHostPort(
  endpoint: string, 
  provisionalProtocol: 'http' | 'https'
): { 
  hadScheme: boolean; 
  host: string; 
  port?: number; 
  extras: { 
    droppedPath?: boolean; 
    droppedQuery?: boolean; 
    droppedCreds?: boolean 
  } 
} {
  let url: URL | undefined;
  const extras: { droppedPath?: boolean; droppedQuery?: boolean; droppedCreds?: boolean } = {};
  
  // Check if endpoint already has a scheme
  const hasScheme = /^https?:\/\//i.test(endpoint);
  
  // Try parsing as full URL first
  try {
    if (hasScheme) {
      url = new URL(endpoint);
    } else {
      // Not a full URL; try host[:port] by attaching provisional scheme
      // Remove anything after first '/' for safety
      const cleanEndpoint = endpoint.replace(/\/.*/, '');
      url = new URL(`${provisionalProtocol}://${cleanEndpoint}`);
    }
  } catch (e) {
    throw new Error(`Unable to parse endpoint '${endpoint}'.`);
  }
  
  // Check for dropped components
  if (url.username || url.password) extras.droppedCreds = true;
  if (url.pathname && url.pathname !== '/') extras.droppedPath = true;
  if (url.search) extras.droppedQuery = true;

  const hadScheme = hasScheme;
  const host = url.hostname; // hostnames lowercased by URL; IPs preserved
  const port = url.port ? Number(url.port) : undefined;

  return { hadScheme, host, port, extras };
}

export function normalizeStorageDescriptor(
  input: plugins.tsclass.storage.IStorageDescriptor,
  logger?: { warn: (msg: string) => void }
): { normalized: INormalizedStorageConfig; warnings: IStorageWarning[] } {
  const warnings: IStorageWarning[] = [];
  const logWarn = (w: IStorageWarning) => { 
    warnings.push(w); 
    if (logger) {
      logger.warn(`[SmartBucket] ${w.code}: ${w.message}`);
    } else {
      console.warn(`[SmartBucket] ${w.code}: ${w.message}`);
    }
  };

  // Coerce and sanitize inputs
  const { value: coercedUseSsl, warning: useSslWarn } = coerceBooleanMaybe((input as any).useSsl);
  if (useSslWarn) logWarn(useSslWarn);
  
  const { value: coercedPort, warning: portWarn } = coercePortMaybe((input as any).port);
  if (portWarn) logWarn(portWarn);

  const { value: endpointStr, warnings: endpointSanWarnings } = sanitizeEndpointString((input as any).endpoint);
  endpointSanWarnings.forEach(logWarn);

  if (!endpointStr) {
    throw new Error('Storage endpoint is required (got empty string). Provide hostname or URL.');
  }

  // Provisional protocol selection for parsing host:port forms
  const provisionalProtocol: 'http' | 'https' = coercedUseSsl === false ? 'http' : 'https';

  const { hadScheme, host, port: epPort, extras } = parseEndpointHostPort(endpointStr, provisionalProtocol);

  if (extras.droppedCreds) {
    logWarn({ 
      code: 'SBK_S3_DROPPED_CREDENTIALS', 
      message: 'Ignored credentials in endpoint URL.' 
    });
  }
  if (extras.droppedPath) {
    logWarn({ 
      code: 'SBK_S3_DROPPED_PATH', 
      message: 'Removed path segment from endpoint URL; S3 endpoint should be host[:port] only.' 
    });
  }
  if (extras.droppedQuery) {
    logWarn({ 
      code: 'SBK_S3_DROPPED_QUERY', 
      message: 'Removed query string from endpoint URL; S3 endpoint should be host[:port] only.' 
    });
  }

  // Final protocol decision
  let finalProtocol: 'http' | 'https';
  if (hadScheme) {
    // Scheme from endpoint wins
    const schemeFromEndpoint = endpointStr.trim().toLowerCase().startsWith('http://') ? 'http' : 'https';
    finalProtocol = schemeFromEndpoint;
    if (typeof coercedUseSsl === 'boolean') {
      const expected = coercedUseSsl ? 'https' : 'http';
      if (expected !== finalProtocol) {
        logWarn({ 
          code: 'SBK_S3_SCHEME_CONFLICT', 
          message: `useSsl=${String(coercedUseSsl)} conflicts with endpoint scheme '${finalProtocol}'; using endpoint scheme.` 
        });
      }
    }
  } else {
    if (typeof coercedUseSsl === 'boolean') {
      finalProtocol = coercedUseSsl ? 'https' : 'http';
    } else {
      finalProtocol = 'https';
      logWarn({ 
        code: 'SBK_S3_GUESSED_PROTOCOL', 
        message: "No scheme in endpoint and useSsl not provided; defaulting to 'https'." 
      });
    }
  }

  // Final port decision
  let finalPort: number | undefined = undefined;
  if (coercedPort !== undefined && epPort !== undefined && coercedPort !== epPort) {
    logWarn({ 
      code: 'SBK_S3_PORT_CONFLICT', 
      message: `Port in config (${coercedPort}) conflicts with endpoint port (${epPort}); using config port.` 
    });
    finalPort = coercedPort;
  } else {
    finalPort = (coercedPort !== undefined) ? coercedPort : epPort;
  }

  // Build canonical endpoint URL (origin only, no trailing slash)
  const url = new URL(`${finalProtocol}://${host}`);
  if (finalPort !== undefined) url.port = String(finalPort);
  const endpointUrl = url.origin;

  const region = input.region || 'us-east-1';

  return {
    normalized: {
      endpointUrl,
      host,
      protocol: finalProtocol,
      port: finalPort,
      region,
      credentials: {
        accessKeyId: input.accessKey,
        secretAccessKey: input.accessSecret,
      },
      forcePathStyle: true,
    },
    warnings,
  };
}