import type { IProjectRepository, ProjectWithRelations } from '../repositories/project.repository.js';
import type { IMcpServerRepository } from '../repositories/interfaces.js';
import { CreateProjectSchema, UpdateProjectSchema } from '../validation/project.schema.js';
import { NotFoundError, ConflictError } from './mcp-server.service.js';
import type { McpConfig } from './mcp-config-generator.js';

export class ProjectService {
  constructor(
    private readonly projectRepo: IProjectRepository,
    private readonly serverRepo: IMcpServerRepository,
  ) {}

  async list(ownerId?: string): Promise<ProjectWithRelations[]> {
    return this.projectRepo.findAll(ownerId);
  }

  async getById(id: string): Promise<ProjectWithRelations> {
    const project = await this.projectRepo.findById(id);
    if (project === null) {
      throw new NotFoundError(`Project not found: ${id}`);
    }
    return project;
  }

  /** Resolve by ID or name. */
  async resolveAndGet(idOrName: string): Promise<ProjectWithRelations> {
    // Try by ID first
    const byId = await this.projectRepo.findById(idOrName);
    if (byId !== null) return byId;

    // Fall back to name
    const byName = await this.projectRepo.findByName(idOrName);
    if (byName !== null) return byName;

    throw new NotFoundError(`Project not found: ${idOrName}`);
  }

  async create(input: unknown, ownerId: string): Promise<ProjectWithRelations> {
    const data = CreateProjectSchema.parse(input);

    const existing = await this.projectRepo.findByName(data.name);
    if (existing !== null) {
      throw new ConflictError(`Project already exists: ${data.name}`);
    }

    // Resolve server names to IDs
    const serverIds = await this.resolveServerNames(data.servers);

    const project = await this.projectRepo.create({
      name: data.name,
      description: data.description,
      prompt: data.prompt,
      ownerId,
      proxyModel: data.proxyModel,
      gated: data.gated,
      ...(data.llmProvider !== undefined ? { llmProvider: data.llmProvider } : {}),
      ...(data.llmModel !== undefined ? { llmModel: data.llmModel } : {}),
      ...(data.serverOverrides !== undefined ? { serverOverrides: data.serverOverrides } : {}),
    });

    // Link servers
    if (serverIds.length > 0) {
      await this.projectRepo.setServers(project.id, serverIds);
    }

    // Re-fetch to include relations
    return this.getById(project.id);
  }

  async update(id: string, input: unknown): Promise<ProjectWithRelations> {
    const data = UpdateProjectSchema.parse(input);
    const project = await this.getById(id);

    // Build update data for scalar fields
    const updateData: Record<string, unknown> = {};
    if (data.description !== undefined) updateData['description'] = data.description;
    if (data.prompt !== undefined) updateData['prompt'] = data.prompt;
    if (data.proxyModel !== undefined) updateData['proxyModel'] = data.proxyModel;
    if (data.llmProvider !== undefined) updateData['llmProvider'] = data.llmProvider;
    if (data.llmModel !== undefined) updateData['llmModel'] = data.llmModel;
    if (data.gated !== undefined) updateData['gated'] = data.gated;
    if (data.serverOverrides !== undefined) updateData['serverOverrides'] = data.serverOverrides;

    // Update scalar fields if any changed
    if (Object.keys(updateData).length > 0) {
      await this.projectRepo.update(project.id, updateData);
    }

    // Update servers if provided
    if (data.servers !== undefined) {
      const serverIds = await this.resolveServerNames(data.servers);
      await this.projectRepo.setServers(project.id, serverIds);
    }

    // Re-fetch to include updated relations
    return this.getById(project.id);
  }

  async delete(id: string): Promise<void> {
    await this.getById(id);
    await this.projectRepo.delete(id);
  }

  async generateMcpConfig(idOrName: string): Promise<McpConfig> {
    const project = await this.resolveAndGet(idOrName);

    // All traffic goes through mcplocal proxy — single entry
    return {
      mcpServers: {
        [project.name]: {
          url: `http://localhost:3100/api/v1/mcp/proxy/project/${project.name}`,
        },
      },
    };
  }

  async addServer(idOrName: string, serverName: string): Promise<ProjectWithRelations> {
    const project = await this.resolveAndGet(idOrName);
    const server = await this.serverRepo.findByName(serverName);
    if (server === null) throw new NotFoundError(`Server not found: ${serverName}`);
    await this.projectRepo.addServer(project.id, server.id);
    return this.getById(project.id);
  }

  async removeServer(idOrName: string, serverName: string): Promise<ProjectWithRelations> {
    const project = await this.resolveAndGet(idOrName);
    const server = await this.serverRepo.findByName(serverName);
    if (server === null) throw new NotFoundError(`Server not found: ${serverName}`);
    await this.projectRepo.removeServer(project.id, server.id);
    return this.getById(project.id);
  }

  private async resolveServerNames(names: string[]): Promise<string[]> {
    return Promise.all(names.map(async (name) => {
      const server = await this.serverRepo.findByName(name);
      if (server === null) throw new NotFoundError(`Server not found: ${name}`);
      return server.id;
    }));
  }

  // ── Backup/restore helpers ──

  async upsertByName(data: Record<string, unknown>, ownerId: string): Promise<ProjectWithRelations> {
    const name = data['name'] as string;
    const servers = (data['servers'] ?? []) as string[];
    const existing = await this.projectRepo.findByName(name);

    const scalarFields: Record<string, unknown> = {};
    if (data['description'] !== undefined) scalarFields['description'] = data['description'];
    if (data['prompt'] !== undefined) scalarFields['prompt'] = data['prompt'];
    if (data['proxyModel'] !== undefined) scalarFields['proxyModel'] = data['proxyModel'];
    if (data['gated'] !== undefined) scalarFields['gated'] = data['gated'];
    if (data['llmProvider'] !== undefined) scalarFields['llmProvider'] = data['llmProvider'];
    if (data['llmModel'] !== undefined) scalarFields['llmModel'] = data['llmModel'];
    if (data['serverOverrides'] !== undefined) scalarFields['serverOverrides'] = data['serverOverrides'];

    if (existing !== null) {
      if (Object.keys(scalarFields).length > 0) {
        await this.projectRepo.update(existing.id, scalarFields);
      }
      if (servers.length > 0) {
        const serverIds = await this.resolveServerNames(servers);
        await this.projectRepo.setServers(existing.id, serverIds);
      }
      return this.getById(existing.id);
    }

    const project = await this.projectRepo.create({
      name,
      description: (data['description'] as string) ?? '',
      ownerId,
      ...scalarFields,
    } as Parameters<IProjectRepository['create']>[0]);

    if (servers.length > 0) {
      const serverIds = await this.resolveServerNames(servers);
      await this.projectRepo.setServers(project.id, serverIds);
    }

    return this.getById(project.id);
  }

  async deleteByName(name: string): Promise<void> {
    const existing = await this.projectRepo.findByName(name);
    if (existing === null) return;
    await this.projectRepo.delete(existing.id);
  }
}
