import { describe, it, expect, beforeEach } from 'vitest'
import { z } from 'zod'
import { ResourceRegistry } from '../resource-registry'
import type { RemoteOrgConfig } from '../resource-registry'
import type { AgentDefinition } from '../../../execution/engine/agent/core/types'
import type { WorkflowDefinition } from '../../../execution/engine/workflow/types'
import type { ModelConfig } from '../../../execution/engine/llm/model-info'

describe('ResourceRegistry — Static Resources & Reserved IDs', () => {
  const mockModelConfig: ModelConfig = {
    provider: 'mock',
    apiKey: 'test-key',
    model: 'mock'
  } as ModelConfig

  const createMockAgent = (resourceId: string, status: 'dev' | 'prod' = 'prod'): AgentDefinition => ({
    config: {
      resourceId,
      name: `Agent ${resourceId}`,
      description: `Test agent ${resourceId}`,
      version: '1.0.0',
      type: 'agent',
      status,
      systemPrompt: 'Test prompt'
    },
    contract: {
      inputSchema: z.object({ message: z.string() })
    },
    tools: [],
    modelConfig: mockModelConfig
  })

  const createMockWorkflow = (resourceId: string): WorkflowDefinition => ({
    config: {
      resourceId,
      name: `Workflow ${resourceId}`,
      description: `Test workflow ${resourceId}`,
      version: '1.0.0',
      type: 'workflow',
      status: 'dev'
    },
    contract: {
      inputSchema: z.object({ data: z.string() }),
      outputSchema: z.object({ result: z.boolean() })
    },
    steps: {
      step1: {
        id: 'step1',
        name: 'Step 1',
        description: 'Step 1',
        handler: async () => ({ result: true }),
        inputSchema: z.object({ data: z.string() }),
        outputSchema: z.object({ result: z.boolean() }),
        next: null
      }
    },
    entryPoint: 'step1'
  })

  const remoteConfig: RemoteOrgConfig = {
    storagePath: 'test-org/dep-1/bundle.js',
    deploymentId: 'dep-1'
  }

  let registry: ResourceRegistry

  beforeEach(() => {
    registry = new ResourceRegistry({})
  })

  // ==========================================
  // registerStaticResources
  // ==========================================

  describe('registerStaticResources', () => {
    it('registers static agent under a new org', () => {
      const agent = createMockAgent('command-center-assistant')
      registry.registerStaticResources('system', { agents: [agent] })

      const def = registry.getResourceDefinition('system', 'command-center-assistant')
      expect(def).toBe(agent)
      expect(def?.config.type).toBe('agent')
    })

    it('static resources have no remote config', () => {
      const agent = createMockAgent('command-center-assistant')
      registry.registerStaticResources('system', { agents: [agent] })

      expect(registry.getRemoteConfig('system', 'command-center-assistant')).toBeNull()
      expect(registry.isRemote('system')).toBe(false)
    })

    it('merges static resources into existing org', () => {
      // First, register a workflow via registerOrganization
      const workflow = createMockWorkflow('my-workflow')
      registry.registerOrganization('my-org', { workflows: [workflow] }, remoteConfig)

      // Then add static agent to same org
      const agent = createMockAgent('static-agent')
      registry.registerStaticResources('my-org', { agents: [agent] })

      // Both resources should be accessible
      expect(registry.getResourceDefinition('my-org', 'my-workflow')).toBe(workflow)
      expect(registry.getResourceDefinition('my-org', 'static-agent')).toBe(agent)
    })

    it('throws on duplicate resource ID within static batch', () => {
      const agent1 = createMockAgent('dup-agent')
      const agent2 = createMockAgent('dup-agent')

      expect(() => registry.registerStaticResources('system', { agents: [agent1, agent2] })).toThrow(
        "Duplicate resource ID 'dup-agent'"
      )
    })

    it('throws on conflict with existing resource in the org', () => {
      const agent1 = createMockAgent('my-agent')
      registry.registerStaticResources('system', { agents: [agent1] })

      const agent2 = createMockAgent('my-agent')
      expect(() => registry.registerStaticResources('system', { agents: [agent2] })).toThrow(
        'conflicts with existing resource'
      )
    })

    it('updates serialization cache after registration', () => {
      const agent = createMockAgent('command-center-assistant')
      registry.registerStaticResources('system', { agents: [agent] })

      const list = registry.getResourceList('system')
      expect(list.agents.length).toBe(1)
      expect(list.agents[0].resourceId).toBe('command-center-assistant')
      expect(list.total).toBe(1)
    })
  })

  // ==========================================
  // Reserved ID rejection in registerOrganization
  // ==========================================

  describe('registerOrganization — reserved ID rejection', () => {
    it('rejects external deployment with reserved agent ID', () => {
      const agent = createMockAgent('command-center-assistant')
      expect(() => registry.registerOrganization('evil-org', { agents: [agent] }, remoteConfig)).toThrow(
        "Resource ID 'command-center-assistant' is reserved for platform use"
      )
    })

    it('rejects external deployment with reserved workflow ID', () => {
      const workflow = createMockWorkflow('command-center-assistant')
      expect(() => registry.registerOrganization('evil-org', { workflows: [workflow] }, remoteConfig)).toThrow(
        "Resource ID 'command-center-assistant' is reserved for platform use"
      )
    })

    it('allows external deployment with non-reserved IDs', () => {
      const agent = createMockAgent('my-custom-agent')
      expect(() => registry.registerOrganization('legit-org', { agents: [agent] }, remoteConfig)).not.toThrow()

      expect(registry.getResourceDefinition('legit-org', 'my-custom-agent')).toBe(agent)
    })
  })

  // ==========================================
  // unregisterOrganization — preserves static
  // ==========================================

  describe('unregisterOrganization — preserves static resources', () => {
    it('removes remote resources but keeps static ones', () => {
      // Register static agent first
      const staticAgent = createMockAgent('static-agent')
      registry.registerStaticResources('my-org', { agents: [staticAgent] })

      // Deploy a remote workflow
      const remoteWorkflow = createMockWorkflow('remote-workflow')
      registry.registerOrganization('my-org', { workflows: [remoteWorkflow] }, remoteConfig)

      // Both should exist
      expect(registry.getResourceDefinition('my-org', 'static-agent')).toBe(staticAgent)
      expect(registry.getResourceDefinition('my-org', 'remote-workflow')).toBe(remoteWorkflow)

      // Unregister the org (only removes remote resources)
      registry.unregisterOrganization('my-org')

      // Static agent still there, remote workflow gone
      expect(registry.getResourceDefinition('my-org', 'static-agent')).toBe(staticAgent)
      expect(registry.getResourceDefinition('my-org', 'remote-workflow')).toBeNull()
    })

    it('org remains in registry when it still has static resources', () => {
      const staticAgent = createMockAgent('static-agent')
      registry.registerStaticResources('my-org', { agents: [staticAgent] })

      const remoteAgent = createMockAgent('remote-agent')
      registry.registerOrganization('my-org', { agents: [remoteAgent] }, remoteConfig)

      registry.unregisterOrganization('my-org')

      // Org should still show in listAll since static resources remain
      const all = registry.listAllResources()
      expect(all['my-org']).toBeDefined()
      expect(all['my-org'].agents?.length).toBe(1)
      expect(all['my-org'].agents?.[0].config.resourceId).toBe('static-agent')
    })

    it('fully removes org when no resources remain after unregister', () => {
      const remoteAgent = createMockAgent('remote-agent')
      registry.registerOrganization('temp-org', { agents: [remoteAgent] }, remoteConfig)

      registry.unregisterOrganization('temp-org')

      const all = registry.listAllResources()
      expect(all['temp-org']).toBeUndefined()
    })

    it('is a no-op when org has no remote resources', () => {
      const staticAgent = createMockAgent('static-agent')
      registry.registerStaticResources('system', { agents: [staticAgent] })

      // Should not throw or remove anything
      registry.unregisterOrganization('system')

      expect(registry.getResourceDefinition('system', 'static-agent')).toBe(staticAgent)
    })
  })

  // ==========================================
  // Startup ordering: static before remote
  // ==========================================

  describe('startup ordering — static registered before remote', () => {
    it('simulates main.ts startup: registerStaticResources then registerOrganization', () => {
      // Step 1: Register system resources (mirrors main.ts)
      const assistant = createMockAgent('command-center-assistant')
      registry.registerStaticResources('system', { agents: [assistant] })

      // Step 2: External deployment tries to claim reserved ID (should fail)
      const evilAgent = createMockAgent('command-center-assistant')
      expect(() => registry.registerOrganization('evil-org', { agents: [evilAgent] }, remoteConfig)).toThrow(
        'reserved for platform use'
      )

      // Step 3: Legit deployment works fine
      const legitAgent = createMockAgent('crm-agent')
      registry.registerOrganization('legit-org', { agents: [legitAgent] }, remoteConfig)

      // Both system and legit resources available
      expect(registry.getResourceDefinition('system', 'command-center-assistant')).toBe(assistant)
      expect(registry.getResourceDefinition('legit-org', 'crm-agent')).toBe(legitAgent)
    })

    it('redeployment preserves system resources', () => {
      // Initial setup
      const assistant = createMockAgent('command-center-assistant')
      registry.registerStaticResources('system', { agents: [assistant] })

      const deployedAgent = createMockAgent('user-agent')
      registry.registerOrganization('user-org', { agents: [deployedAgent] }, remoteConfig)

      // Redeploy user-org (unregister + re-register)
      registry.unregisterOrganization('user-org')
      const newAgent = createMockAgent('user-agent-v2')
      registry.registerOrganization(
        'user-org',
        { agents: [newAgent] },
        {
          ...remoteConfig,
          deploymentId: 'dep-2'
        }
      )

      // System resource unaffected
      expect(registry.getResourceDefinition('system', 'command-center-assistant')).toBe(assistant)
      // User org has new version
      expect(registry.getResourceDefinition('user-org', 'user-agent-v2')).toBe(newAgent)
      expect(registry.getResourceDefinition('user-org', 'user-agent')).toBeNull()
    })
  })

  // ==========================================
  // remoteResources tracking
  // ==========================================

  describe('remote resource tracking', () => {
    it('static resources are not tracked as remote', () => {
      const agent = createMockAgent('static-agent')
      registry.registerStaticResources('system', { agents: [agent] })

      expect(registry.isRemote('system')).toBe(false)
      expect(registry.getRemoteConfig('system', 'static-agent')).toBeNull()
      expect(registry.getAnyRemoteConfig('system')).toBeNull()
    })

    it('registered org resources are tracked as remote', () => {
      const agent = createMockAgent('user-agent')
      registry.registerOrganization('user-org', { agents: [agent] }, remoteConfig)

      expect(registry.isRemote('user-org')).toBe(true)
      expect(registry.getRemoteConfig('user-org', 'user-agent')).toEqual(remoteConfig)
    })

    it('getRemoteStats counts correctly with mixed static and remote', () => {
      // Static (should not count)
      const assistant = createMockAgent('assistant')
      registry.registerStaticResources('system', { agents: [assistant] })

      // Remote (should count)
      const agent1 = createMockAgent('agent-1')
      const agent2 = createMockAgent('agent-2')
      registry.registerOrganization('org-a', { agents: [agent1] }, remoteConfig)
      registry.registerOrganization(
        'org-b',
        { agents: [agent2] },
        {
          ...remoteConfig,
          deploymentId: 'dep-2'
        }
      )

      const stats = registry.getRemoteStats()
      expect(stats.activeOrgs).toBe(2)
      expect(stats.totalResources).toBe(2)
    })
  })

  // ==========================================
  // listResourcesForOrganization — origin field
  // ==========================================

  describe('listResourcesForOrganization — origin field', () => {
    it('static resources show origin: local', () => {
      const agent = createMockAgent('static-agent')
      registry.registerStaticResources('system', { agents: [agent] })

      const list = registry.listResourcesForOrganization('system')
      expect(list.agents[0].origin).toBe('local')
    })

    it('remote resources show origin: remote', () => {
      const agent = createMockAgent('remote-agent')
      registry.registerOrganization('user-org', { agents: [agent] }, remoteConfig)

      const list = registry.listResourcesForOrganization('user-org')
      expect(list.agents[0].origin).toBe('remote')
    })
  })
})
