/**
 * Model Resolver
 * Resolves agent names and roles to concrete model identifiers using tier system
 *
 * @implements @.aiwg/architecture/ADR-015-enhanced-model-selection.md
 * @architecture @.aiwg/architecture/enhanced-model-selection-design.md
 */

import { ConfigLoader } from './config-loader.js';
import type {
  ModelTier,
  ModelRole,
  Provider,
  ResolvedModel,
  ModelResolverOptions,
  MergedModelConfig,
  ModelInfo,
  TierInfo,
} from './types.js';

/**
 * Resolves models for agents using tier system
 */
export class ModelResolver {
  private config: MergedModelConfig | null = null;
  private readonly loader: ConfigLoader;
  private readonly options: ModelResolverOptions;

  constructor(options: ModelResolverOptions = {}, projectPath?: string) {
    this.options = {
      respectMinimums: true,
      ...options,
    };
    this.loader = new ConfigLoader(projectPath);
  }

  /**
   * Resolve model for an agent
   *
   * @param agentName - Name of the agent
   * @param agentRole - Role hint from agent frontmatter (legacy 'model' field)
   * @param agentTier - Tier from agent frontmatter (new 'model-tier' field)
   * @returns Resolved model information
   */
  async resolve(
    agentName: string,
    agentRole?: ModelRole,
    agentTier?: ModelTier
  ): Promise<ResolvedModel> {
    const config = await this.getConfig();

    // Determine effective tier and role
    const { tier, role, source } = this.determineEffectiveTierAndRole(
      agentName,
      agentRole,
      agentTier,
      config
    );

    // Check for direct model override (bypasses tier system)
    const override = config.agentOverrides[agentName];
    if (override?.['model-override']) {
      return {
        modelId: override['model-override'],
        provider: this.options.provider || config.defaults.provider,
        tier,
        role,
        source: 'agent',
      };
    }

    // Resolve tier + role to concrete model
    const modelId = this.resolveModelId(tier, role, config);

    return {
      modelId,
      provider: this.options.provider || config.defaults.provider,
      tier,
      role,
      source,
    };
  }

  /**
   * Determine effective tier and role for an agent
   */
  private determineEffectiveTierAndRole(
    agentName: string,
    agentRole: ModelRole | undefined,
    agentTier: ModelTier | undefined,
    config: MergedModelConfig
  ): { tier: ModelTier; role: ModelRole; source: ResolvedModel['source'] } {
    // 1. CLI tier takes precedence
    if (this.options.tier) {
      const role = this.determineRole(agentName, agentRole, config);
      return {
        tier: this.applyMinimumTier(agentName, this.options.tier, config),
        role,
        source: 'cli',
      };
    }

    // 2. Agent frontmatter tier
    const agentOverride = config.agentOverrides[agentName];
    if (agentTier || agentOverride?.['model-tier']) {
      const tier = agentTier || agentOverride!['model-tier']!;
      const role = this.determineRole(agentName, agentRole, config);
      return {
        tier: this.applyMinimumTier(agentName, tier, config),
        role,
        source: 'agent',
      };
    }

    // 3. Default tier from config
    const defaultTier = config.defaults.tier;
    const role = this.determineRole(agentName, agentRole, config);

    return {
      tier: this.applyMinimumTier(agentName, defaultTier, config),
      role,
      source: 'default',
    };
  }

  /**
   * Determine role for agent
   */
  private determineRole(
    agentName: string,
    agentRole?: ModelRole,
    config?: MergedModelConfig
  ): ModelRole {
    // If explicit role provided, use it
    if (agentRole) {
      return agentRole;
    }

    // Check agent role assignments
    if (config?.agentRoleAssignments) {
      for (const category of Object.values(config.agentRoleAssignments)) {
        if (category.agents.includes(agentName)) {
          return category.defaultRole;
        }
      }
    }

    // Default to 'coding' as reasonable middle ground
    return 'coding';
  }

  /**
   * Apply minimum tier constraint if respectMinimums is true
   */
  private applyMinimumTier(
    agentName: string,
    requestedTier: ModelTier,
    config: MergedModelConfig
  ): ModelTier {
    if (!this.options.respectMinimums) {
      return requestedTier;
    }

    // Find agent's minimum tier
    for (const category of Object.values(config.agentRoleAssignments || {})) {
      if (category.agents.includes(agentName)) {
        const minimumTier = category.minimumTier;
        return this.maxTier(requestedTier, minimumTier);
      }
    }

    return requestedTier;
  }

  /**
   * Return the higher of two tiers
   */
  private maxTier(tier1: ModelTier, tier2: ModelTier): ModelTier {
    const tierOrder: ModelTier[] = ['economy', 'standard', 'premium', 'max-quality'];
    const index1 = tierOrder.indexOf(tier1);
    const index2 = tierOrder.indexOf(tier2);
    return tierOrder[Math.max(index1, index2)];
  }

  /**
   * Resolve tier + role to concrete model ID
   */
  private resolveModelId(tier: ModelTier, role: ModelRole, config: MergedModelConfig): string {
    const provider = this.options.provider || config.defaults.provider;
    let providerDef = config.providers[provider];

    if (!providerDef) {
      throw new Error(`Provider ${provider} not found in configuration`);
    }

    // Resolve inheritance
    providerDef = this.loader.resolveProviderInheritance(providerDef, config.providers);

    // Check for tier-specific override first
    if (providerDef.tierOverrides?.[tier]?.[role]) {
      return providerDef.tierOverrides[tier][role]!;
    }

    // Apply tier's role mapping
    const tierDef = config.tiers[tier];
    if (!tierDef) {
      throw new Error(`Tier ${tier} not found in configuration`);
    }

    const mappedRole = tierDef.roleMapping[role];

    // Get model for mapped role
    const modelDef = providerDef.models?.[mappedRole];
    if (!modelDef) {
      throw new Error(`Model not found for provider ${provider}, role ${mappedRole}`);
    }

    return modelDef.default;
  }

  /**
   * Get model info by ID or alias
   */
  async getModelInfo(modelIdOrAlias: string): Promise<ModelInfo | null> {
    const config = await this.getConfig();
    const provider = this.options.provider || config.defaults.provider;
    let providerDef = config.providers[provider];

    if (!providerDef) {
      return null;
    }

    // Resolve inheritance
    providerDef = this.loader.resolveProviderInheritance(providerDef, config.providers);

    // Check shorthand aliases
    const resolvedId = config.shorthand[modelIdOrAlias] || modelIdOrAlias;

    // Search through all roles
    for (const [roleName, modelDef] of Object.entries(providerDef.models || {})) {
      if (modelDef.default === resolvedId || modelDef.aliases?.includes(modelIdOrAlias)) {
        return {
          id: modelDef.default,
          provider,
          role: roleName as ModelRole,
          aliases: modelDef.aliases || [],
          capabilities: modelDef.capabilities || [],
          contextWindow: modelDef.contextWindow || 0,
          costPer1kTokens: modelDef.costPer1kTokens,
        };
      }
    }

    return null;
  }

  /**
   * List all available models for provider
   */
  async listModels(provider?: Provider): Promise<ModelInfo[]> {
    const config = await this.getConfig();
    const targetProvider = provider || this.options.provider || config.defaults.provider;
    let providerDef = config.providers[targetProvider];

    if (!providerDef) {
      return [];
    }

    // Resolve inheritance
    providerDef = this.loader.resolveProviderInheritance(providerDef, config.providers);

    const models: ModelInfo[] = [];

    for (const [roleName, modelDef] of Object.entries(providerDef.models || {})) {
      models.push({
        id: modelDef.default,
        provider: targetProvider,
        role: roleName as ModelRole,
        aliases: modelDef.aliases || [],
        capabilities: modelDef.capabilities || [],
        contextWindow: modelDef.contextWindow || 0,
        costPer1kTokens: modelDef.costPer1kTokens,
      });
    }

    return models;
  }

  /**
   * List available tiers
   */
  async listTiers(): Promise<TierInfo[]> {
    const config = await this.getConfig();

    return Object.entries(config.tiers).map(([tierName, tierDef]) => ({
      tier: tierName as ModelTier,
      description: tierDef.description,
      costMultiplier: tierDef.costMultiplier,
    }));
  }

  /**
   * Get configuration (cached)
   */
  private async getConfig(): Promise<MergedModelConfig> {
    if (!this.config) {
      this.config = await this.loader.load();
    }
    return this.config;
  }

  /**
   * Clear cache (useful for testing)
   */
  clearCache(): void {
    this.config = null;
    this.loader.clearCache();
  }
}
