package parser

import (
	"context"
	"log/slog"
	"runtime"
	"sync"
	"time"

	contractevents "bit.admedia.com/scm/ad/adpilot-indexing-code-parser.com/internal/contracts/events"
	"bit.admedia.com/scm/ad/adpilot-indexing-code-parser.com/internal/contracts/graph"
	"bit.admedia.com/scm/ad/adpilot-indexing-code-parser.com/internal/store"
)

const (
	GraphSchemaVersion = graph.SchemaVersionV1

	JobStatusPending   = "pending"
	JobStatusRunning   = "running"
	JobStatusCompleted = "completed"
	JobStatusFailed    = "failed"
	JobStatusSkipped   = "skipped"
)

// ServiceConfig holds runtime settings for parse orchestration.
type ServiceConfig struct {
	WorkspacePath             string // GIT_WORKSPACE_PATH
	Workers                   int    // PARSER_WORKERS
	ParserVersion             string // PARSER_VERSION
	CodeParseFailureThreshold float64
	Logger                    *slog.Logger
}

// ParseJob represents a parser job for a file changed event.
type ParseJob struct {
	JobID      string `json:"job_id"`
	RepoID     string `json:"repo_id"`
	SnapshotID string `json:"snapshot_id"`
	CommitSHA  string `json:"commit_sha"`
	FilePath   string `json:"file_path"`
	Language   string `json:"language"`
	Status     string `json:"status"`
	ArtifactID string `json:"artifact_id,omitempty"`
}

// LanguageAnalyzer defines language-specific parsing behavior.
type LanguageAnalyzer interface {
	SupportedLanguage() string
	ParseFile(ctx context.Context, source []byte, filePath string) (*graph.Artifact, []contractevents.CodeChunk, error)
}

// FallbackAnalyzer parses unsupported languages using tiered fallback strategies.
type FallbackAnalyzer interface {
	ParseFile(ctx context.Context, source []byte, filePath, language string) (*graph.Artifact, []contractevents.CodeChunk, error)
}

// EventConsumer defines Redis Stream consumer boundaries.
type EventConsumer interface {
	ConsumeFilesChanged(ctx context.Context) error
	ConsumeRepoSnapshotReady(ctx context.Context) error
	ConsumeCommitsChanged(ctx context.Context) error
}

// EventPublisher defines future Redis Stream publisher boundaries.
type EventPublisher interface {
	PublishChunksReady(ctx context.Context, event contractevents.ChunksReadyEvent) error
	PublishGraphArtifactReady(ctx context.Context, event contractevents.GraphArtifactReadyEvent) error
	PublishGraphDeltaReady(ctx context.Context, event contractevents.GraphDeltaReadyEvent) error
	PublishParserDiagnostics(ctx context.Context, event contractevents.ParserDiagnosticsReadyEvent) error
}

// ServiceOption configures optional parser service dependencies.
type ServiceOption func(*Service)

// WithFinalizeStore wires Mongo snapshot finalization and indexing-run fan-in.
func WithFinalizeStore(store FinalizeStore) ServiceOption {
	return func(s *Service) {
		s.finalize = store
	}
}

// WithCommitOrderStore wires ordered commits.changed processing.
func WithCommitOrderStore(store CommitOrderStore) ServiceOption {
	return func(s *Service) {
		s.commitOrder = store
	}
}

// Service coordinates parsing workflows.
type Service struct {
	cfg       ServiceConfig
	analyzers map[string]LanguageAnalyzer
	fallback  FallbackAnalyzer
	events    EventPublisher
	artifacts store.ArtifactStore
	finalize  FinalizeStore
	commitOrder CommitOrderStore

	snapshotsMu sync.Mutex
	snapshots   map[string]snapshotContext

	jobsMu         sync.RWMutex
	jobs           map[string]ParseJob
	diagnosticsMu  sync.RWMutex
	jobDiagnostics map[string][]contractevents.ParserDiagnostic
}

// WithFallbackAnalyzer wires tiered fallback parsing for unsupported languages.
func WithFallbackAnalyzer(fallback FallbackAnalyzer) ServiceOption {
	return func(s *Service) {
		s.fallback = fallback
	}
}

// NewService creates a parser service with injected dependencies.
func NewService(cfg ServiceConfig, analyzers []LanguageAnalyzer, events EventPublisher, artifacts store.ArtifactStore, opts ...ServiceOption) *Service {
	if cfg.Workers <= 0 {
		cfg.Workers = runtime.NumCPU()
	}
	if cfg.ParserVersion == "" {
		cfg.ParserVersion = "v0.1.0"
	}
	if cfg.CodeParseFailureThreshold <= 0 {
		cfg.CodeParseFailureThreshold = 0.05
	}

	analyzerMap := make(map[string]LanguageAnalyzer, len(analyzers))
	for _, analyzer := range analyzers {
		analyzerMap[analyzer.SupportedLanguage()] = analyzer
	}

	svc := &Service{
		cfg:            cfg,
		analyzers:      analyzerMap,
		events:         events,
		artifacts:      artifacts,
		snapshots:      make(map[string]snapshotContext),
		jobs:           make(map[string]ParseJob),
		jobDiagnostics: make(map[string][]contractevents.ParserDiagnostic),
	}
	for _, opt := range opts {
		opt(svc)
	}
	return svc
}

func (s *Service) Status() string {
	return "idle"
}

func (s *Service) SupportedLanguages() []string {
	languages := make([]string, 0, len(s.analyzers))
	for language := range s.analyzers {
		languages = append(languages, language)
	}
	return languages
}

// GetParseJob returns the current status of a parse job by ID.
func (s *Service) GetParseJob(jobID string) (ParseJob, bool) {
	s.jobsMu.RLock()
	defer s.jobsMu.RUnlock()

	job, ok := s.jobs[jobID]
	return job, ok
}

// GetJobDiagnostics returns diagnostics for a known parse job.
// The second return value is false when the job ID is unknown.
func (s *Service) GetJobDiagnostics(jobID string) ([]contractevents.ParserDiagnostic, bool) {
	s.jobsMu.RLock()
	_, ok := s.jobs[jobID]
	s.jobsMu.RUnlock()
	if !ok {
		return nil, false
	}

	s.diagnosticsMu.RLock()
	defer s.diagnosticsMu.RUnlock()

	diagnostics, ok := s.jobDiagnostics[jobID]
	if !ok {
		return []contractevents.ParserDiagnostic{}, true
	}
	return diagnostics, true
}

func (s *Service) setJobDiagnostics(jobID string, diagnostics []contractevents.ParserDiagnostic) {
	if len(diagnostics) == 0 {
		return
	}

	s.diagnosticsMu.Lock()
	defer s.diagnosticsMu.Unlock()
	s.jobDiagnostics[jobID] = diagnostics
}

func (s *Service) setJobArtifactID(jobID, artifactID string) {
	s.jobsMu.Lock()
	defer s.jobsMu.Unlock()

	job, ok := s.jobs[jobID]
	if !ok {
		return
	}
	job.ArtifactID = artifactID
	s.jobs[jobID] = job
}

func (s *Service) setJob(job ParseJob) {
	s.jobsMu.Lock()
	defer s.jobsMu.Unlock()
	s.jobs[job.JobID] = job
}

func (s *Service) updateJobStatus(jobID, status string) {
	s.jobsMu.Lock()
	defer s.jobsMu.Unlock()

	job, ok := s.jobs[jobID]
	if !ok {
		return
	}
	job.Status = status
	s.jobs[jobID] = job
}

func (s *Service) parseJobFromEvent(event contractevents.FileChangedEvent, jobID, status string) ParseJob {
	return ParseJob{
		JobID:      jobID,
		RepoID:     event.RepoID,
		SnapshotID: event.SnapshotID,
		CommitSHA:  event.CommitSHA,
		FilePath:   event.FilePath,
		Language:   event.Language,
		Status:     status,
	}
}

// ActiveJobCount returns the number of parse jobs currently in running status.
func (s *Service) ActiveJobCount() int {
	s.jobsMu.RLock()
	defer s.jobsMu.RUnlock()

	count := 0
	for _, job := range s.jobs {
		if job.Status == JobStatusRunning {
			count++
		}
	}
	return count
}

// WaitForJobs blocks until no running jobs remain or ctx is cancelled.
func (s *Service) WaitForJobs(ctx context.Context) {
	ticker := time.NewTicker(50 * time.Millisecond)
	defer ticker.Stop()

	for {
		if s.ActiveJobCount() == 0 {
			return
		}
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
		}
	}
}
