/*
Copyright © 2025 Benny Powers <web@bennypowers.com>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package completion_test

import (
	"os"
	"path/filepath"
	"strings"
	"sync"
	"testing"
	"testing/synctest"

	"bennypowers.dev/cem/internal/platform"
	"bennypowers.dev/cem/lsp"
	M "bennypowers.dev/cem/manifest"
	W "bennypowers.dev/cem/workspace"
)

// TestGenerateIntegrationFast tests manifest reloading with virtual time
// This eliminates timing dependencies from the original E2E test
func TestGenerateIntegrationFast(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		// Create a temporary workspace directory that mimics a real project
		tempDir := t.TempDir()

		// Create package.json
		packageJSON := `{
  "name": "test-project", 
  "customElements": "dist/custom-elements.json"
}`

		err := os.WriteFile(filepath.Join(tempDir, "package.json"), []byte(packageJSON), 0644)
		if err != nil {
			t.Fatalf("Failed to create package.json: %v", err)
		}

		// Create cem.yaml config
		configDir := filepath.Join(tempDir, ".config")
		err = os.MkdirAll(configDir, 0755)
		if err != nil {
			t.Fatalf("Failed to create config directory: %v", err)
		}

		cemConfig := `generate:
  files:
    - src/test-alert.ts
`
		err = os.WriteFile(filepath.Join(configDir, "cem.yaml"), []byte(cemConfig), 0644)
		if err != nil {
			t.Fatalf("Failed to create cem.yaml: %v", err)
		}

		// Create src directory and initial TypeScript file
		srcDir := filepath.Join(tempDir, "src")
		err = os.MkdirAll(srcDir, 0755)
		if err != nil {
			t.Fatalf("Failed to create src directory: %v", err)
		}

		initialTSContent := `import {LitElement, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';

@customElement('test-alert')
export class TestAlert extends LitElement {
  @property() state: 'info' | 'success' | 'warning' = 'info';
  
  render() {
    return html` + "`<div class=\"alert ${this.state}\"><slot></slot></div>`" + `;
  }
}
`

		tsFilePath := filepath.Join(srcDir, "test-alert.ts")
		err = os.WriteFile(tsFilePath, []byte(initialTSContent), 0644)
		if err != nil {
			t.Fatalf("Failed to create TypeScript file: %v", err)
		}

		// Create initial manifest (would normally be generated by CEM analyzer)
		distDir := filepath.Join(tempDir, "dist")
		err = os.MkdirAll(distDir, 0755)
		if err != nil {
			t.Fatalf("Failed to create dist directory: %v", err)
		}

		initialManifest := `{
  "schemaVersion": "1.0.0",
  "readme": "",
  "modules": [
    {
      "kind": "javascript-module",
      "path": "src/test-alert.ts",
      "declarations": [
        {
          "kind": "class",
          "description": "",
          "name": "TestAlert",
          "customElement": true,
          "tagName": "test-alert",
          "attributes": [
            {
              "name": "state",
              "type": {
                "text": "\"info\" | \"success\" | \"warning\""
              },
              "default": "\"info\""
            }
          ]
        }
      ],
      "exports": [
        {
          "kind": "custom-element-definition",
          "name": "test-alert",
          "declaration": {
            "name": "TestAlert",
            "module": "src/test-alert.ts"
          }
        }
      ]
    }
  ]
}`

		manifestPath := filepath.Join(distDir, "custom-elements.json")
		err = os.WriteFile(manifestPath, []byte(initialManifest), 0644)
		if err != nil {
			t.Fatalf("Failed to create initial manifest: %v", err)
		}

		// Create workspace and registry with MockFileWatcher
		workspace := W.NewFileSystemWorkspaceContext(tempDir)
		err = workspace.Init()
		if err != nil {
			t.Fatalf("Failed to initialize workspace: %v", err)
		}

		mockFileWatcher := platform.NewMockFileWatcher()
		registry := lsp.NewRegistry(mockFileWatcher)

		// Load initial manifests
		err = registry.LoadFromWorkspace(workspace)
		if err != nil {
			t.Fatalf("Failed to load workspace manifests: %v", err)
		}

		// Test initial attribute value completions directly using registry
		// Verify the element is loaded correctly
		element, exists := registry.Element("test-alert")
		if !exists {
			t.Fatalf("Expected test-alert element to be loaded")
		}
		t.Logf("Initial element loaded: %s with %d attributes", element.TagName, len(element.Attributes))

		// Verify initial attributes
		attrs, attrsExist := registry.Attributes("test-alert")
		if !attrsExist {
			t.Fatalf("Expected test-alert attributes to be loaded")
		}
		stateAttr, stateExists := attrs["state"]
		if !stateExists {
			t.Fatalf("Expected state attribute to exist")
		}
		t.Logf("Initial state attribute type: %s", stateAttr.Type.Text)

		// Verify we can get the expected completion values from the type
		expectedInitial := []string{"info", "success", "warning"}
		typeText := stateAttr.Type.Text
		for _, expected := range expectedInitial {
			if !strings.Contains(typeText, `"`+expected+`"`) {
				t.Errorf("Expected initial type to contain '%s', got: %s", expected, typeText)
			}
		}

		// Set up MockGenerateWatcher with callback that updates the manifest
		var generateCallbackMu sync.Mutex
		generateCallback := func() error {
			generateCallbackMu.Lock()
			defer generateCallbackMu.Unlock()

			// Simulate the generate process creating an updated manifest
			updatedManifest := strings.Replace(initialManifest,
				`"text": "\"info\" | \"success\" | \"warning\""`,
				`"text": "\"info\" | \"success\" | \"warning\" | \"error\""`,
				1)

			// Write updated manifest to disk (simulating generate process)
			err := os.WriteFile(manifestPath, []byte(updatedManifest), 0644)
			if err != nil {
				t.Logf("Failed to write updated manifest: %v", err)
				return err
			}

			// Parse the updated manifest and add to registry
			var pkg M.Package
			err = pkg.UnmarshalJSON([]byte(updatedManifest))
			if err != nil {
				t.Logf("Failed to parse updated manifest: %v", err)
				return err
			}

			// Add the updated manifest to the registry
			registry.AddManifest(&pkg)
			return nil
		}

		mockGenerateWatcher := platform.NewMockGenerateWatcher(generateCallback)
		registry.SetGenerateWatcher(mockGenerateWatcher)

		// Start the generate watcher (which is now mocked)
		err = registry.StartGenerateWatcher()
		if err != nil {
			t.Fatalf("Failed to start generate watcher: %v", err)
		}
		defer func() { _ = registry.StopGenerateWatcher() }()

		// Update the TypeScript file to add 'error' state
		updatedTSContent := strings.Replace(initialTSContent,
			`state: 'info' | 'success' | 'warning' = 'info'`,
			`state: 'info' | 'success' | 'warning' | 'error' = 'info'`,
			1)

		err = os.WriteFile(tsFilePath, []byte(updatedTSContent), 0644)
		if err != nil {
			t.Fatalf("Failed to update TypeScript file: %v", err)
		}

		// Trigger generate watcher with MockGenerateWatcher - instant with virtual time
		err = mockGenerateWatcher.TriggerGenerate()
		if err != nil {
			t.Fatalf("Failed to trigger generate: %v", err)
		}

		// Yield to allow any goroutines to process
		var quickWait sync.WaitGroup
		quickWait.Add(1)
		go func() {
			defer quickWait.Done()
		}()
		quickWait.Wait()

		// Test updated attributes - should now include 'error'
		updatedAttrs, updatedAttrsExist := registry.Attributes("test-alert")
		if !updatedAttrsExist {
			t.Fatalf("Expected test-alert attributes to still exist after update")
		}
		updatedStateAttr, updatedStateExists := updatedAttrs["state"]
		if !updatedStateExists {
			t.Fatalf("Expected state attribute to still exist after update")
		}
		t.Logf("Updated state attribute type: %s", updatedStateAttr.Type.Text)

		// Verify updated completions contain 'error'
		expectedUpdated := []string{"info", "success", "warning", "error"}
		updatedTypeText := updatedStateAttr.Type.Text
		for _, expected := range expectedUpdated {
			if !strings.Contains(updatedTypeText, `"`+expected+`"`) {
				t.Errorf("Expected updated type to contain '%s', got: %s", expected, updatedTypeText)
			}
		}

		t.Logf("✅ Test passed: Generate integration with synctest completed instantly")
	})
}
