package internetinit

import (
	"bytes"
	"context"
	"fmt"
	"math"

	foodbootstrap "github.com/Benjamin2037/tipoc-tv/industries/internet/food/bootstrap"
	martbootstrap "github.com/Benjamin2037/tipoc-tv/industries/internet/mart/bootstrap"
	internetbootstrap "github.com/Benjamin2037/tipoc-tv/industries/internet/platform/bootstrap"
	internetruntime "github.com/Benjamin2037/tipoc-tv/industries/internet/platform/runtime"
	ridebootstrap "github.com/Benjamin2037/tipoc-tv/industries/internet/ride/bootstrap"
	userbootstrap "github.com/Benjamin2037/tipoc-tv/industries/internet/user/bootstrap"
)

const internetBusinessOrderSegmentSize uint32 = 4096

type CompiledInit struct {
	Manifest    internetruntime.CompiledInternetManifest
	ManifestRef internetruntime.ArtifactRef
	Artifacts   []internetbootstrap.SealedGenerationArtifact
}

func CompileGraph(ctx context.Context, runProfileID string, compiledScenario internetruntime.CompiledInternetScenario,
	composition WorldComposition, bindings [4]DatabaseCatalogBinding, horizontal internetruntime.InternetHorizontalPlan,
) (CompiledInit, error) {
	if compiledScenario.CSVObjectTargetBytes > math.MaxInt64 {
		return CompiledInit{}, fmt.Errorf("internet CSV object target exceeds int64")
	}
	if err := internetruntime.ValidateInternetHorizontalPlan(horizontal); err != nil {
		return CompiledInit{}, err
	}
	if horizontal.RunID != compiledScenario.RunID || horizontal.AttemptID != compiledScenario.AttemptID {
		return CompiledInit{}, fmt.Errorf("internet horizontal plan identity differs from the scenario")
	}
	selectedDomains, err := selectedInternetDatabaseDomains(compiledScenario)
	if err != nil {
		return CompiledInit{}, err
	}
	if err := validateSelectedInternetCatalogBindings(compiledScenario, bindings, selectedDomains); err != nil {
		return CompiledInit{}, err
	}
	if err := validateWorldChildrenScenarioBinding(compiledScenario, composition.Children); err != nil {
		return CompiledInit{}, err
	}
	workers, err := internetbootstrap.BuildGenerationWorkersFromHorizontalPlan(horizontal)
	if err != nil {
		return CompiledInit{}, err
	}
	userPlan, err := userbootstrap.BuildGenerationPlan(runProfileID, composition.UserRowPlan,
		int64(compiledScenario.CSVObjectTargetBytes), workers)
	if err != nil {
		return CompiledInit{}, err
	}
	generationPlans := make([]internetbootstrap.GenerationPlan, 1, 1+len(compiledScenario.SelectedBusinesses))
	generationPlans[0] = userPlan
	for _, business := range compiledScenario.SelectedBusinesses {
		var plan internetbootstrap.GenerationPlan
		switch business {
		case internetruntime.BusinessRide:
			plan, err = ridebootstrap.BuildGenerationPlan(runProfileID, composition.RideCardinalityInput,
				composition.RideCardinalityPlan, int64(compiledScenario.CSVObjectTargetBytes), workers)
		case internetruntime.BusinessFood:
			plan, err = foodbootstrap.BuildGenerationPlan(runProfileID, composition.FoodCardinalityInput,
				composition.FoodCardinalityPlan, int64(compiledScenario.CSVObjectTargetBytes), workers)
		case internetruntime.BusinessMart:
			plan, err = martbootstrap.BuildGenerationPlan(runProfileID, composition.MartCardinalityInput,
				composition.MartCardinalityPlan, int64(compiledScenario.CSVObjectTargetBytes), workers)
		default:
			return CompiledInit{}, fmt.Errorf("internet init selected business %s is unsupported", business)
		}
		if err != nil {
			return CompiledInit{}, err
		}
		generationPlans = append(generationPlans, plan)
	}
	memory := internetInitMemoryStore{payloads: map[string][]byte{}}
	children, err := compileInternetChildArtifacts(composition.Children)
	if err != nil {
		return CompiledInit{}, err
	}
	for _, artifact := range children {
		memory.payloads[artifact.Ref.Path] = append([]byte(nil), artifact.Payload...)
	}
	identityRefs, streamRefs, sliceArtifacts, err := compileInternetRangeSliceArtifacts(ctx, memory, children,
		compiledScenario.SelectedBusinesses)
	if err != nil {
		return CompiledInit{}, err
	}
	for _, artifact := range sliceArtifacts {
		memory.payloads[artifact.Ref.Path] = append([]byte(nil), artifact.Payload...)
	}
	world, err := internetbootstrap.CompileInternetWorldGenerationPlan(ctx, internetbootstrap.WorldGenerationPlanInput{
		Scenario: compiledScenario, HorizontalPlan: horizontal,
		GeographyRef: children[0].Ref, UserSubjectsRef: children[1].Ref, ParticipationRef: children[2].Ref,
		HistoricalDemandRef: children[3].Ref, IdentityRangesRef: children[4].Ref, StreamRangesRef: children[5].Ref,
		IdentityRanges: composition.Children.IdentityRanges, StreamRanges: composition.Children.StreamRanges,
		IdentitySliceRefs: identityRefs, StreamSliceRefs: streamRefs, GenerationPlans: generationPlans,
		ArtifactReader: memory, ArtifactPrefix: "internet-generation",
	})
	if err != nil {
		return CompiledInit{}, err
	}
	order, planned, err := internetruntime.CompileGlobalBusinessOrderPlanWithPlannedOwnership(ctx, internetruntime.BusinessOrderPlanInput{
		Scenario: compiledScenario, UserSubjects: composition.Children.UserSubjects, WorldPlanDigest: world.Plan.PlanDigest,
		SegmentSize: internetBusinessOrderSegmentSize, ArtifactStore: memory,
	}, horizontal)
	if err != nil {
		return CompiledInit{}, err
	}
	orderArtifact, err := internetInitValueArtifact("internet-business-order-plan", "internet/orders/plan.json",
		internetruntime.OrderPlanSchemaVersion, order.Plan)
	if err != nil {
		return CompiledInit{}, err
	}
	plannedArtifact, err := internetInitValueArtifact(internetruntime.PlannedActionOwnershipArtifactID,
		internetruntime.PlannedActionOwnershipPath, internetruntime.PlannedActionOwnershipSchemaVersion, planned)
	if err != nil {
		return CompiledInit{}, err
	}
	if err := internetruntime.ValidatePlannedActionOwnershipRef(plannedArtifact.Ref); err != nil {
		return CompiledInit{}, err
	}
	horizontalArtifact, err := internetInitValueArtifact("internet-horizontal-plan", "internet/horizontal-plan.json",
		internetruntime.HorizontalPlanSchemaVersion, horizontal)
	if err != nil {
		return CompiledInit{}, err
	}
	compiledManifest := internetruntime.CompiledInternetManifest{SchemaVersion: internetruntime.ManifestSchemaVersion,
		Scenario: compiledScenario, WorldGenerationPlanRef: world.PlanArtifact.Ref,
		BusinessOrderPlanRef: orderArtifact.Ref, HorizontalPlanRef: horizontalArtifact.Ref,
		PlannedActionOwnershipRef: plannedArtifact.Ref}
	if err := internetruntime.SealCompiledInternetManifest(&compiledManifest); err != nil {
		return CompiledInit{}, err
	}
	manifestArtifact, err := internetInitValueArtifact("internet-manifest", "init/internet-manifest.json",
		internetruntime.ManifestSchemaVersion, compiledManifest)
	if err != nil {
		return CompiledInit{}, err
	}
	artifacts := make([]internetbootstrap.SealedGenerationArtifact, 0, 32)
	for _, domain := range selectedDomains {
		binding, _, ok := internetCatalogBindingForDomain(bindings, domain)
		if !ok {
			return CompiledInit{}, fmt.Errorf("internet init is missing the selected %s catalog binding", domain)
		}
		artifacts = append(artifacts, internetbootstrap.SealedGenerationArtifact{Ref: binding.Binding.CatalogRef,
			Payload: append([]byte(nil), binding.Payload...)})
	}
	artifacts = append(artifacts, children...)
	artifacts = append(artifacts, sliceArtifacts...)
	for _, slice := range world.Slices {
		artifacts = append(artifacts, slice.TablePlansArtifact, slice.WorkUnitsArtifact, slice.ObjectPlansArtifact)
	}
	artifacts = append(artifacts, world.PlanArtifact, horizontalArtifact)
	for _, entry := range order.Index.Entries {
		payload := memory.payloads[entry.SegmentRef.Path]
		artifacts = append(artifacts, internetbootstrap.SealedGenerationArtifact{Ref: entry.SegmentRef, Payload: append([]byte(nil), payload...)})
	}
	artifacts = append(artifacts, internetbootstrap.SealedGenerationArtifact{Ref: order.Plan.SegmentIndexRef,
		Payload: append([]byte(nil), memory.payloads[order.Plan.SegmentIndexRef.Path]...)})
	artifacts = append(artifacts, orderArtifact, plannedArtifact, manifestArtifact)
	return CompiledInit{Manifest: compiledManifest, ManifestRef: manifestArtifact.Ref, Artifacts: artifacts}, nil
}

type internetInitMemoryStore struct{ payloads map[string][]byte }

func (store internetInitMemoryStore) ReadArtifact(_ context.Context, path string) ([]byte, error) {
	payload, ok := store.payloads[path]
	if !ok {
		return nil, fmt.Errorf("internet init artifact %s is missing", path)
	}
	return append([]byte(nil), payload...), nil
}

func (store internetInitMemoryStore) PublishArtifact(_ context.Context, path string, payload []byte) error {
	if current, ok := store.payloads[path]; ok && string(current) != string(payload) {
		return fmt.Errorf("internet init artifact %s conflicts", path)
	}
	store.payloads[path] = append([]byte(nil), payload...)
	return nil
}

func internetInitValueArtifact(id, path, schema string, value any) (internetbootstrap.SealedGenerationArtifact, error) {
	payload, err := internetruntime.CanonicalJSON(value)
	if err != nil {
		return internetbootstrap.SealedGenerationArtifact{}, err
	}
	ref := internetruntime.ArtifactRef{SchemaVersion: internetruntime.ArtifactRefSchemaVersion,
		PayloadSchemaVersion: schema, ArtifactID: id, Path: path, PayloadDigest: internetruntime.DigestBytes(payload), Bytes: uint64(len(payload))}
	if err := internetruntime.ValidateArtifactRef(ref, schema, "internet_init_artifact", "internet-init.compile"); err != nil {
		return internetbootstrap.SealedGenerationArtifact{}, err
	}
	return internetbootstrap.SealedGenerationArtifact{Ref: ref, Payload: payload}, nil
}

func compileInternetChildArtifacts(children internetbootstrap.InternetWorldChildren) ([]internetbootstrap.SealedGenerationArtifact, error) {
	refs := []struct {
		id, path, schema string
		value            any
	}{
		{"internet-geography", "internet/children/geography.json", internetruntime.GeographyPlanSchemaVersion, children.Geography},
		{"internet-user-subjects", "internet/children/user-subjects.json", internetruntime.UserSubjectPlanSchemaVersion, children.UserSubjects},
		{"internet-participation", "internet/children/participation.json", internetruntime.ParticipationPlanSchemaVersion, children.Participation},
		{"internet-historical-demand", "internet/children/historical-demand.json", internetruntime.HistoricalDemandSchemaVersion, children.HistoricalDemand},
		{"internet-identity-ranges", "internet/children/identity-ranges.json", internetruntime.IdentityRangePlanSchemaVersion, children.IdentityRanges},
		{"internet-stream-ranges", "internet/children/stream-ranges.json", internetruntime.StreamRangePlanSchemaVersion, children.StreamRanges},
	}
	output := make([]internetbootstrap.SealedGenerationArtifact, len(refs))
	for index, item := range refs {
		artifact, err := internetInitValueArtifact(item.id, item.path, item.schema, item.value)
		if err != nil {
			return nil, err
		}
		output[index] = artifact
	}
	return output, nil
}

func compileInternetRangeSliceArtifacts(ctx context.Context, reader internetInitMemoryStore,
	children []internetbootstrap.SealedGenerationArtifact, selected []internetruntime.BusinessID,
) ([]internetruntime.ArtifactRef, []internetruntime.ArtifactRef, []internetbootstrap.SealedGenerationArtifact, error) {
	domains := make([]internetruntime.DatabaseDomain, 1, 1+len(selected))
	domains[0] = internetruntime.DatabaseUser
	for _, business := range selected {
		domain, ok := map[internetruntime.BusinessID]internetruntime.DatabaseDomain{
			internetruntime.BusinessRide: internetruntime.DatabaseRide,
			internetruntime.BusinessFood: internetruntime.DatabaseFood,
			internetruntime.BusinessMart: internetruntime.DatabaseMart,
		}[business]
		if !ok {
			return nil, nil, nil, fmt.Errorf("internet init selected business %s has no generation domain", business)
		}
		domains = append(domains, domain)
	}
	identityRefs := make([]internetruntime.ArtifactRef, len(domains))
	streamRefs := make([]internetruntime.ArtifactRef, len(domains))
	artifacts := make([]internetbootstrap.SealedGenerationArtifact, 0, len(domains)*2)
	for index, domain := range domains {
		identitySlice, err := internetruntime.CompileIdentityRangeSlice(ctx, reader, children[4].Ref, domain)
		if err != nil {
			return nil, nil, nil, err
		}
		identityArtifact, err := internetInitValueArtifact("internet-identity-slice-"+string(domain),
			"internet/slices/identity-slice-"+string(domain)+".json", internetruntime.IdentityRangeSliceSchemaVersion, identitySlice)
		if err != nil {
			return nil, nil, nil, err
		}
		streamSlice, err := internetruntime.CompileStreamRangeSlice(ctx, reader, children[5].Ref, domain)
		if err != nil {
			return nil, nil, nil, err
		}
		streamArtifact, err := internetInitValueArtifact("internet-stream-slice-"+string(domain),
			"internet/slices/stream-slice-"+string(domain)+".json", internetruntime.StreamRangeSliceSchemaVersion, streamSlice)
		if err != nil {
			return nil, nil, nil, err
		}
		identityRefs[index], streamRefs[index] = identityArtifact.Ref, streamArtifact.Ref
		artifacts = append(artifacts, identityArtifact, streamArtifact)
	}
	return identityRefs, streamRefs, artifacts, nil
}

type Authorities struct {
	World       internetruntime.InternetWorldGenerationPlan
	Horizontal  internetruntime.InternetHorizontalPlan
	Order       internetruntime.GlobalBusinessOrderPlan
	Planned     internetruntime.PlannedActionOwnership
	Composition WorldComposition
}

func ReadAuthorities(ctx context.Context, reader internetruntime.ArtifactReader,
	manifest internetruntime.CompiledInternetManifest,
) (Authorities, error) {
	selectedDomains, err := selectedInternetDatabaseDomains(manifest.Scenario)
	if err != nil {
		return Authorities{}, err
	}
	for _, domain := range selectedDomains {
		database, ok := internetScenarioDatabaseBinding(manifest.Scenario, domain)
		if !ok {
			return Authorities{}, fmt.Errorf("internet manifest is missing the selected %s database binding", domain)
		}
		recipe, ok := internetDatabaseCatalogRecipeForDomain(database.Domain)
		if !ok {
			return Authorities{}, fmt.Errorf("internet manifest contains an unknown database domain")
		}
		expected, err := compileInternetDatabaseCatalogBinding(recipe, database.DatabaseName)
		if err != nil {
			return Authorities{}, err
		}
		expected.Binding = database
		if _, err := ReadDatabaseCatalogBinding(ctx, reader, expected); err != nil {
			return Authorities{}, err
		}
	}
	world, err := internetruntime.ReadInternetWorldGenerationPlan(ctx, reader, manifest.WorldGenerationPlanRef)
	if err != nil {
		return Authorities{}, err
	}
	horizontal, err := internetruntime.ReadInternetHorizontalPlan(ctx, reader, manifest.HorizontalPlanRef)
	if err != nil {
		return Authorities{}, err
	}
	order, err := internetruntime.ReadGlobalBusinessOrderPlan(ctx, reader, manifest.BusinessOrderPlanRef)
	if err != nil {
		return Authorities{}, err
	}
	if err := internetruntime.ValidateScenarioOrderHotspotBinding(manifest.Scenario, order); err != nil {
		return Authorities{}, err
	}
	planned, err := internetruntime.ReadPlannedActionOwnership(ctx, reader, manifest.PlannedActionOwnershipRef)
	if err != nil {
		return Authorities{}, err
	}
	if err := internetruntime.ValidatePlannedActionOwnershipCoverage(order, horizontal, planned); err != nil {
		return Authorities{}, err
	}
	children, err := readInternetWorldChildren(ctx, reader, world)
	if err != nil {
		return Authorities{}, err
	}
	for _, database := range world.DatabaseSlices {
		identitySlice, err := internetruntime.ReadIdentityRangeSlice(ctx, reader, database.IdentitySliceRef)
		if err != nil {
			return Authorities{}, err
		}
		if err := internetruntime.ValidateIdentityRangeSliceLineage(identitySlice, children.IdentityRanges); err != nil {
			return Authorities{}, err
		}
		streamSlice, err := internetruntime.ReadStreamRangeSlice(ctx, reader, database.StreamSliceRef)
		if err != nil {
			return Authorities{}, err
		}
		if err := internetruntime.ValidateStreamRangeSliceLineage(streamSlice, children.StreamRanges); err != nil {
			return Authorities{}, err
		}
	}
	composition, err := CompileWorldChildrenForScenario(manifest.Scenario)
	if err != nil {
		return Authorities{}, err
	}
	if err := validateWorldChildrenAgainstExpected(children, composition.Children); err != nil {
		return Authorities{}, err
	}
	return Authorities{World: world, Horizontal: horizontal, Order: order, Planned: planned, Composition: composition}, nil
}

func selectedInternetDatabaseDomains(scenario internetruntime.CompiledInternetScenario) ([]internetruntime.DatabaseDomain, error) {
	if err := internetruntime.ValidateCompiledInternetScenario(scenario); err != nil {
		return nil, err
	}
	domains := make([]internetruntime.DatabaseDomain, 1, 1+len(scenario.SelectedBusinesses))
	domains[0] = internetruntime.DatabaseUser
	for _, business := range scenario.SelectedBusinesses {
		var domain internetruntime.DatabaseDomain
		switch business {
		case internetruntime.BusinessRide:
			domain = internetruntime.DatabaseRide
		case internetruntime.BusinessFood:
			domain = internetruntime.DatabaseFood
		case internetruntime.BusinessMart:
			domain = internetruntime.DatabaseMart
		default:
			return nil, fmt.Errorf("internet selected business %s has no database domain", business)
		}
		domains = append(domains, domain)
	}
	return domains, nil
}

func internetScenarioDatabaseBinding(scenario internetruntime.CompiledInternetScenario,
	domain internetruntime.DatabaseDomain,
) (internetruntime.DatabaseBinding, bool) {
	for _, binding := range scenario.Databases {
		if binding.Domain == domain {
			return binding, true
		}
	}
	return internetruntime.DatabaseBinding{}, false
}

func internetCatalogBindingForDomain(bindings [4]DatabaseCatalogBinding,
	domain internetruntime.DatabaseDomain,
) (DatabaseCatalogBinding, int, bool) {
	var candidate DatabaseCatalogBinding
	count := 0
	for _, binding := range bindings {
		if binding.Binding.Domain == domain {
			candidate = binding
			count++
		}
	}
	return candidate, count, count > 0
}

func validateSelectedInternetCatalogBindings(scenario internetruntime.CompiledInternetScenario,
	bindings [4]DatabaseCatalogBinding, selectedDomains []internetruntime.DatabaseDomain,
) error {
	for _, domain := range selectedDomains {
		scenarioBinding, ok := internetScenarioDatabaseBinding(scenario, domain)
		if !ok {
			return fmt.Errorf("internet scenario is missing the selected %s database binding", domain)
		}
		candidate, count, ok := internetCatalogBindingForDomain(bindings, domain)
		if !ok {
			return fmt.Errorf("internet init is missing the selected %s catalog binding", domain)
		}
		if count != 1 {
			return fmt.Errorf("internet selected %s catalog binding is duplicated", domain)
		}
		recipe, ok := internetDatabaseCatalogRecipeForDomain(domain)
		if !ok {
			return fmt.Errorf("internet selected database %s has no catalog recipe", domain)
		}
		canonical, err := compileInternetDatabaseCatalogBinding(recipe, scenarioBinding.DatabaseName)
		if err != nil {
			return err
		}
		if scenarioBinding != canonical.Binding {
			return fmt.Errorf("internet scenario %s catalog/schema authority differs from production catalog", domain)
		}
		if candidate.Binding != canonical.Binding || !bytes.Equal(candidate.Payload, canonical.Payload) {
			return fmt.Errorf("internet selected %s catalog binding differs from production authority", domain)
		}
	}
	return nil
}

func readInternetWorldChildren(ctx context.Context, reader internetruntime.ArtifactReader,
	world internetruntime.InternetWorldGenerationPlan,
) (internetbootstrap.InternetWorldChildren, error) {
	geography, err := internetruntime.ReadGeographyWorldUnitPlan(ctx, reader, world.GeographyRef)
	if err != nil {
		return internetbootstrap.InternetWorldChildren{}, err
	}
	subjects, err := internetruntime.ReadUserSubjectPlan(ctx, reader, world.UserSubjectsRef)
	if err != nil {
		return internetbootstrap.InternetWorldChildren{}, err
	}
	participation, err := internetruntime.ReadUserBusinessParticipationPlan(ctx, reader, world.ParticipationRef)
	if err != nil {
		return internetbootstrap.InternetWorldChildren{}, err
	}
	history, err := internetruntime.ReadHistoricalDemandPlan(ctx, reader, world.HistoricalDemandRef)
	if err != nil {
		return internetbootstrap.InternetWorldChildren{}, err
	}
	identities, err := internetruntime.ReadIdentityRangePlan(ctx, reader, world.IdentityRangesRef)
	if err != nil {
		return internetbootstrap.InternetWorldChildren{}, err
	}
	streams, err := internetruntime.ReadStreamRangePlan(ctx, reader, world.StreamRangesRef)
	if err != nil {
		return internetbootstrap.InternetWorldChildren{}, err
	}
	return internetbootstrap.InternetWorldChildren{Geography: geography, UserSubjects: subjects, Participation: participation,
		HistoricalDemand: history, IdentityRanges: identities, StreamRanges: streams}, nil
}
