package main

import (
	"context"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"strings"

	"sub2api-cn-relay-manager/internal/config"
	"sub2api-cn-relay-manager/internal/host/sub2api"
	"sub2api-cn-relay-manager/internal/pack"
	"sub2api-cn-relay-manager/internal/provision"
	"sub2api-cn-relay-manager/internal/reconcile"
	"sub2api-cn-relay-manager/internal/store/sqlite"
)

type installPackFunc func(context.Context, installPackCLIRequest) (provision.PackInstallResult, error)
type importProviderFunc func(context.Context, importCLIRequest) (provision.ImportReport, error)
type previewProviderFunc func(context.Context, previewCLIRequest) (provision.PreviewReport, error)
type rollbackProviderFunc func(context.Context, rollbackCLIRequest) (rollbackSummary, error)
type reconcileProviderFunc func(context.Context, reconcileCLIRequest) (reconcile.Result, error)

type installPackCLIRequest struct {
	HostBaseURL     string
	HostAPIKey      string
	HostBearerToken string
	PackPath        string
}

type importCLIRequest struct {
	HostBaseURL       string
	HostAPIKey        string
	HostBearerToken   string
	PackDir           string
	ProviderID        string
	Keys              []string
	Mode              string
	AccessMode        string
	AccessAPIKey      string
	SubscriptionUsers []string
	SubscriptionDays  int
}

type previewCLIRequest struct {
	HostBaseURL     string
	HostAPIKey      string
	HostBearerToken string
	PackDir         string
	ProviderID      string
	Keys            []string
	Mode            string
}

type rollbackCLIRequest struct {
	HostBaseURL     string
	HostAPIKey      string
	HostBearerToken string
	PackDir         string
	ProviderID      string
}

type reconcileCLIRequest struct {
	HostBaseURL     string
	HostAPIKey      string
	HostBearerToken string
	PackDir         string
	ProviderID      string
	AccessAPIKey    string
}

type rollbackSummary struct {
	Accounts int
	Plans    int
	Channels int
	Groups   int
}

func main() {
	if err := execute(context.Background(), log.Writer(), os.Args[1:], func(context.Context) (config.StartupConfig, error) {
		return config.LoadStartupFromEnv()
	}, runInstallPack, runImportProvider, runPreviewProvider, runRollbackProvider, runReconcileProvider, runApplyHostOverlay, runBatchImport); err != nil {
		log.Fatalf("run cli: %v", err)
	}
}

func execute(
	ctx context.Context,
	output io.Writer,
	args []string,
	loadConfig func(context.Context) (config.StartupConfig, error),
	installPack installPackFunc,
	importProvider importProviderFunc,
	previewProvider previewProviderFunc,
	rollbackProvider rollbackProviderFunc,
	reconcileProvider reconcileProviderFunc,
	applyHostOverlay applyHostOverlayFunc,
	batchImport batchImportFunc,
) error {
	if len(args) > 0 && args[0] == "batch-import" {
		req, err := parseBatchImportCLIArgs(args[1:])
		if err != nil {
			return err
		}
		result, err := batchImport(ctx, req)
		if err != nil {
			return err
		}
		_, err = fmt.Fprintf(output, "run_id=%s\nresult_page=%s\n", result.RunID, result.ResultPage)
		return err
	}
	if len(args) > 0 && args[0] == "install-pack" {
		req, err := parseInstallPackCLIArgs(args[1:])
		if err != nil {
			return err
		}
		result, err := installPack(ctx, req)
		if err != nil {
			return err
		}
		_, err = fmt.Fprintf(output, "pack_id=%s\nversion=%s\nhost_version=%s\nproviders=%d\nalready_installed=%t\n", result.Pack.PackID, result.Pack.Version, result.HostVersion, len(result.Providers), result.AlreadyInstalled)
		return err
	}
	if len(args) > 0 && args[0] == "import-provider" {
		req, err := parseImportCLIArgs(args[1:])
		if err != nil {
			return err
		}
		report, err := importProvider(ctx, req)
		if err != nil {
			_, _ = fmt.Fprintf(output, "batch_status=%s\nprovider_status=%s\naccess_status=%s\n", report.BatchStatus, report.ProviderStatus, report.AccessStatus)
			return err
		}
		_, err = fmt.Fprintf(output, "batch_status=%s\nprovider_status=%s\naccess_status=%s\naccounts=%d\n", report.BatchStatus, report.ProviderStatus, report.AccessStatus, len(report.Accounts))
		return err
	}
	if len(args) > 0 && args[0] == "preview-provider" {
		req, err := parsePreviewCLIArgs(args[1:])
		if err != nil {
			return err
		}
		report, err := previewProvider(ctx, req)
		if err != nil {
			return err
		}
		_, err = fmt.Fprintf(output, "accepted_keys=%d\ngroup=%s\nchannel=%s\nplan=%s\n", len(report.AcceptedKeys), report.Decisions["group"].Action, report.Decisions["channel"].Action, report.Decisions["plan"].Action)
		return err
	}
	if len(args) > 0 && args[0] == "rollback-provider" {
		req, err := parseRollbackCLIArgs(args[1:])
		if err != nil {
			return err
		}
		summary, err := rollbackProvider(ctx, req)
		if err != nil {
			return err
		}
		_, err = fmt.Fprintf(output, "deleted_accounts=%d\ndeleted_plans=%d\ndeleted_channels=%d\ndeleted_groups=%d\n", summary.Accounts, summary.Plans, summary.Channels, summary.Groups)
		return err
	}
	if len(args) > 0 && args[0] == "reconcile-provider" {
		req, err := parseReconcileCLIArgs(args[1:])
		if err != nil {
			return err
		}
		result, err := reconcileProvider(ctx, req)
		if err != nil {
			return err
		}
		_, err = fmt.Fprintf(output, "status=%s\nmissing_count=%d\nextra_count=%d\nprobe_failures=%d\naccess_status=%s\n", result.Status, result.MissingCount, result.ExtraCount, result.ProbeFailureCount, result.AccessStatus)
		return err
	}
	if len(args) > 0 && args[0] == "apply-host-overlay" {
		req, err := parseApplyHostOverlayCLIArgs(args[1:])
		if err != nil {
			return err
		}
		result, err := applyHostOverlay(ctx, req)
		if err != nil {
			return err
		}
		overlayIDs := make([]string, 0, len(result.AppliedOverlays))
		for _, hostOverlay := range result.AppliedOverlays {
			overlayIDs = append(overlayIDs, hostOverlay.OverlayID)
		}
		_, err = fmt.Fprintf(output, "output_dir=%s\napplied_overlays=%d\noverlay_ids=%s\nmetadata_file=%s\n", result.OutputDir, len(result.AppliedOverlays), strings.Join(overlayIDs, ","), result.MetadataFilePath)
		return err
	}

	cfg, err := loadConfig(ctx)
	if err != nil {
		return err
	}
	_, err = fmt.Fprintf(output, "sub2api-cn-relay-manager cli ready\nlisten_addr=%s\nsqlite_dsn=%s\n", cfg.Server.ListenAddr, cfg.Database.SQLiteDSN)
	return err
}

func parseInstallPackCLIArgs(args []string) (installPackCLIRequest, error) {
	fs := flag.NewFlagSet("install-pack", flag.ContinueOnError)
	fs.SetOutput(io.Discard)

	var req installPackCLIRequest
	fs.StringVar(&req.HostBaseURL, "host-base-url", "", "")
	fs.StringVar(&req.HostAPIKey, "host-api-key", "", "")
	fs.StringVar(&req.HostBearerToken, "host-bearer-token", "", "")
	fs.StringVar(&req.PackPath, "pack-path", "", "")
	if err := fs.Parse(args); err != nil {
		return installPackCLIRequest{}, err
	}

	switch {
	case strings.TrimSpace(req.HostBaseURL) == "":
		return installPackCLIRequest{}, fmt.Errorf("--host-base-url is required")
	case strings.TrimSpace(req.PackPath) == "":
		return installPackCLIRequest{}, fmt.Errorf("--pack-path is required")
	}
	return req, nil
}

func parseImportCLIArgs(args []string) (importCLIRequest, error) {
	fs := flag.NewFlagSet("import-provider", flag.ContinueOnError)
	fs.SetOutput(io.Discard)

	var req importCLIRequest
	var keysCSV string
	var subscriptionUsersCSV string
	fs.StringVar(&req.HostBaseURL, "host-base-url", "", "")
	fs.StringVar(&req.HostAPIKey, "host-api-key", "", "")
	fs.StringVar(&req.HostBearerToken, "host-bearer-token", "", "")
	fs.StringVar(&req.PackDir, "pack-dir", "", "")
	fs.StringVar(&req.ProviderID, "provider-id", "", "")
	fs.StringVar(&keysCSV, "keys", "", "")
	fs.StringVar(&req.Mode, "mode", provision.ImportModePartial, "")
	fs.StringVar(&req.AccessMode, "access-mode", provision.AccessModeSelfService, "")
	fs.StringVar(&req.AccessAPIKey, "access-api-key", "", "")
	fs.StringVar(&subscriptionUsersCSV, "subscription-users", "", "")
	fs.IntVar(&req.SubscriptionDays, "subscription-days", 30, "")
	if err := fs.Parse(args); err != nil {
		return importCLIRequest{}, err
	}

	req.Keys = splitCSV(keysCSV)
	req.SubscriptionUsers = splitCSV(subscriptionUsersCSV)
	switch {
	case strings.TrimSpace(req.HostBaseURL) == "":
		return importCLIRequest{}, fmt.Errorf("--host-base-url is required")
	case strings.TrimSpace(req.PackDir) == "":
		return importCLIRequest{}, fmt.Errorf("--pack-dir is required")
	case strings.TrimSpace(req.ProviderID) == "":
		return importCLIRequest{}, fmt.Errorf("--provider-id is required")
	case len(req.Keys) == 0:
		return importCLIRequest{}, fmt.Errorf("--keys is required")
	case strings.TrimSpace(req.AccessAPIKey) == "":
		return importCLIRequest{}, fmt.Errorf("--access-api-key is required")
	}
	return req, nil
}

func parsePreviewCLIArgs(args []string) (previewCLIRequest, error) {
	fs := flag.NewFlagSet("preview-provider", flag.ContinueOnError)
	fs.SetOutput(io.Discard)

	var req previewCLIRequest
	var keysCSV string
	fs.StringVar(&req.HostBaseURL, "host-base-url", "", "")
	fs.StringVar(&req.HostAPIKey, "host-api-key", "", "")
	fs.StringVar(&req.HostBearerToken, "host-bearer-token", "", "")
	fs.StringVar(&req.PackDir, "pack-dir", "", "")
	fs.StringVar(&req.ProviderID, "provider-id", "", "")
	fs.StringVar(&keysCSV, "keys", "", "")
	fs.StringVar(&req.Mode, "mode", provision.ImportModePartial, "")
	if err := fs.Parse(args); err != nil {
		return previewCLIRequest{}, err
	}

	req.Keys = splitCSV(keysCSV)
	switch {
	case strings.TrimSpace(req.HostBaseURL) == "":
		return previewCLIRequest{}, fmt.Errorf("--host-base-url is required")
	case strings.TrimSpace(req.PackDir) == "":
		return previewCLIRequest{}, fmt.Errorf("--pack-dir is required")
	case strings.TrimSpace(req.ProviderID) == "":
		return previewCLIRequest{}, fmt.Errorf("--provider-id is required")
	case len(req.Keys) == 0:
		return previewCLIRequest{}, fmt.Errorf("--keys is required")
	}
	return req, nil
}

func parseRollbackCLIArgs(args []string) (rollbackCLIRequest, error) {
	fs := flag.NewFlagSet("rollback-provider", flag.ContinueOnError)
	fs.SetOutput(io.Discard)

	var req rollbackCLIRequest
	fs.StringVar(&req.HostBaseURL, "host-base-url", "", "")
	fs.StringVar(&req.HostAPIKey, "host-api-key", "", "")
	fs.StringVar(&req.HostBearerToken, "host-bearer-token", "", "")
	fs.StringVar(&req.PackDir, "pack-dir", "", "")
	fs.StringVar(&req.ProviderID, "provider-id", "", "")
	if err := fs.Parse(args); err != nil {
		return rollbackCLIRequest{}, err
	}

	switch {
	case strings.TrimSpace(req.HostBaseURL) == "":
		return rollbackCLIRequest{}, fmt.Errorf("--host-base-url is required")
	case strings.TrimSpace(req.PackDir) == "":
		return rollbackCLIRequest{}, fmt.Errorf("--pack-dir is required")
	case strings.TrimSpace(req.ProviderID) == "":
		return rollbackCLIRequest{}, fmt.Errorf("--provider-id is required")
	}
	return req, nil
}

func parseReconcileCLIArgs(args []string) (reconcileCLIRequest, error) {
	fs := flag.NewFlagSet("reconcile-provider", flag.ContinueOnError)
	fs.SetOutput(io.Discard)

	var req reconcileCLIRequest
	fs.StringVar(&req.HostBaseURL, "host-base-url", "", "")
	fs.StringVar(&req.HostAPIKey, "host-api-key", "", "")
	fs.StringVar(&req.HostBearerToken, "host-bearer-token", "", "")
	fs.StringVar(&req.PackDir, "pack-dir", "", "")
	fs.StringVar(&req.ProviderID, "provider-id", "", "")
	fs.StringVar(&req.AccessAPIKey, "access-api-key", "", "")
	if err := fs.Parse(args); err != nil {
		return reconcileCLIRequest{}, err
	}

	switch {
	case strings.TrimSpace(req.HostBaseURL) == "":
		return reconcileCLIRequest{}, fmt.Errorf("--host-base-url is required")
	case strings.TrimSpace(req.PackDir) == "":
		return reconcileCLIRequest{}, fmt.Errorf("--pack-dir is required")
	case strings.TrimSpace(req.ProviderID) == "":
		return reconcileCLIRequest{}, fmt.Errorf("--provider-id is required")
	}
	return req, nil
}

func runInstallPack(ctx context.Context, req installPackCLIRequest) (provision.PackInstallResult, error) {
	loadedPack, err := pack.LoadPath(req.PackPath)
	if err != nil {
		return provision.PackInstallResult{}, err
	}
	client, err := sub2api.NewClient(req.HostBaseURL, sub2api.WithAPIKey(req.HostAPIKey), sub2api.WithBearerToken(req.HostBearerToken))
	if err != nil {
		return provision.PackInstallResult{}, err
	}
	startupConfig, err := config.LoadStartupFromEnv()
	if err != nil {
		return provision.PackInstallResult{}, err
	}
	store, err := sqlite.Open(ctx, startupConfig.Database.SQLiteDSN)
	if err != nil {
		return provision.PackInstallResult{}, err
	}
	defer store.Close()

	service := provision.NewPackInstallService(store, client)
	return service.Install(ctx, provision.PackInstallRequest{Pack: loadedPack})
}

func runImportProvider(ctx context.Context, req importCLIRequest) (provision.ImportReport, error) {
	loadedPack, err := pack.LoadDir(req.PackDir)
	if err != nil {
		return provision.ImportReport{}, err
	}

	providerManifest, err := findProvider(loadedPack, req.ProviderID)
	if err != nil {
		return provision.ImportReport{}, err
	}

	client, err := sub2api.NewClient(req.HostBaseURL, sub2api.WithAPIKey(req.HostAPIKey), sub2api.WithBearerToken(req.HostBearerToken))
	if err != nil {
		return provision.ImportReport{}, err
	}

	startupConfig, err := config.LoadStartupFromEnv()
	if err != nil {
		return provision.ImportReport{}, err
	}
	store, err := sqlite.Open(ctx, startupConfig.Database.SQLiteDSN)
	if err != nil {
		return provision.ImportReport{}, err
	}
	defer store.Close()

	subscriptions := make([]provision.SubscriptionTarget, 0, len(req.SubscriptionUsers))
	for _, userID := range req.SubscriptionUsers {
		subscriptions = append(subscriptions, provision.SubscriptionTarget{UserID: userID, DurationDays: req.SubscriptionDays})
	}

	runtimeService := provision.NewRuntimeImportService(store, client)
	result, err := runtimeService.Import(ctx, provision.RuntimeImportRequest{
		HostBaseURL: req.HostBaseURL,
		Pack:        loadedPack,
		Provider:    providerManifest,
		Mode:        req.Mode,
		Keys:        req.Keys,
		Access: provision.AccessRequest{
			Mode:          req.AccessMode,
			ProbeAPIKey:   req.AccessAPIKey,
			Subscriptions: subscriptions,
		},
	})
	return result.Report, err
}

func runPreviewProvider(ctx context.Context, req previewCLIRequest) (provision.PreviewReport, error) {
	loadedPack, err := pack.LoadDir(req.PackDir)
	if err != nil {
		return provision.PreviewReport{}, err
	}

	providerManifest, err := findProvider(loadedPack, req.ProviderID)
	if err != nil {
		return provision.PreviewReport{}, err
	}

	client, err := sub2api.NewClient(req.HostBaseURL, sub2api.WithAPIKey(req.HostAPIKey), sub2api.WithBearerToken(req.HostBearerToken))
	if err != nil {
		return provision.PreviewReport{}, err
	}

	service := provision.NewPreviewService(client)
	hostVersion, err := client.GetHostVersion(ctx)
	if err != nil {
		return provision.PreviewReport{}, err
	}
	return service.PreviewImport(ctx, provision.PreviewRequest{
		TargetHost:  loadedPack.Manifest.TargetHost,
		HostVersion: hostVersion,
		Provider:    providerManifest,
		Mode:        req.Mode,
		Keys:        req.Keys,
	})
}

func runRollbackProvider(ctx context.Context, req rollbackCLIRequest) (rollbackSummary, error) {
	loadedPack, err := pack.LoadDir(req.PackDir)
	if err != nil {
		return rollbackSummary{}, err
	}

	providerManifest, err := findProvider(loadedPack, req.ProviderID)
	if err != nil {
		return rollbackSummary{}, err
	}

	client, err := sub2api.NewClient(req.HostBaseURL, sub2api.WithAPIKey(req.HostAPIKey), sub2api.WithBearerToken(req.HostBearerToken))
	if err != nil {
		return rollbackSummary{}, err
	}

	service := provision.NewRollbackService(client)
	report, err := service.Rollback(ctx, provision.RollbackRequest{Provider: providerManifest})
	if err != nil {
		return rollbackSummary{}, err
	}
	return rollbackSummary{
		Accounts: report.AccountsDeleted,
		Plans:    report.PlansDeleted,
		Channels: report.ChannelsDeleted,
		Groups:   report.GroupsDeleted,
	}, nil
}

func runReconcileProvider(ctx context.Context, req reconcileCLIRequest) (reconcile.Result, error) {
	loadedPack, err := pack.LoadDir(req.PackDir)
	if err != nil {
		return reconcile.Result{}, err
	}

	providerManifest, err := findProvider(loadedPack, req.ProviderID)
	if err != nil {
		return reconcile.Result{}, err
	}

	client, err := sub2api.NewClient(req.HostBaseURL, sub2api.WithAPIKey(req.HostAPIKey), sub2api.WithBearerToken(req.HostBearerToken))
	if err != nil {
		return reconcile.Result{}, err
	}

	startupConfig, err := config.LoadStartupFromEnv()
	if err != nil {
		return reconcile.Result{}, err
	}
	store, err := sqlite.Open(ctx, startupConfig.Database.SQLiteDSN)
	if err != nil {
		return reconcile.Result{}, err
	}
	defer store.Close()

	hostRow, err := store.Hosts().GetByBaseURL(ctx, req.HostBaseURL)
	if err != nil {
		return reconcile.Result{}, err
	}

	service := reconcile.NewService(store, client)
	return service.Reconcile(ctx, reconcile.Request{HostID: hostRow.HostID, HostBaseURL: req.HostBaseURL, AccessProbeAPIKey: req.AccessAPIKey, Pack: loadedPack, Provider: providerManifest})
}

func findProvider(loaded pack.LoadedPack, providerID string) (pack.ProviderManifest, error) {
	for _, provider := range loaded.Providers {
		if provider.ProviderID == strings.TrimSpace(providerID) {
			return provider, nil
		}
	}
	return pack.ProviderManifest{}, fmt.Errorf("provider %q not found in pack %q", providerID, loaded.Manifest.PackID)
}

func splitCSV(value string) []string {
	parts := strings.Split(value, ",")
	result := make([]string, 0, len(parts))
	for _, part := range parts {
		trimmed := strings.TrimSpace(part)
		if trimmed != "" {
			result = append(result, trimmed)
		}
	}
	return result
}
