package dedicatedapi

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"os"
	"path/filepath"
	"slices"
	"strings"

	"github.com/tidbcloud/controlplane9/internal/host"
)

// RenderHostManifest returns only runtime objects for the host API. The
// admission/RBAC bootstrap for the metadata-only API is deliberately rendered
// separately and must be applied with the Dedicated admin kubeconfig.
func RenderHostManifest(selected *host.Session) ([]byte, error) {
	if selected == nil || selected.DedicatedAPI == nil {
		return nil, fmt.Errorf("render Dedicated API host manifest: prepared session is required")
	}
	state := selected.DedicatedAPI
	if err := validateExistingPKI(state); err != nil {
		return nil, fmt.Errorf("render Dedicated API host manifest: %w", err)
	}
	if strings.TrimSpace(state.APIServerImage) == "" || strings.TrimSpace(state.ControllerManagerImage) == "" {
		return nil, fmt.Errorf("render Dedicated API host manifest: version-matched control-plane images are required")
	}
	if strings.TrimSpace(state.EtcdImage) == "" {
		return nil, fmt.Errorf("render Dedicated API host manifest: etcd image is required")
	}

	secretData, err := controlPlaneSecretData(state)
	if err != nil {
		return nil, err
	}
	secret := bytes.NewBufferString("apiVersion: v1\nkind: Namespace\nmetadata:\n  name: cp9-system\n---\n" +
		"apiVersion: v1\nkind: Secret\nmetadata:\n  name: dedicated-api-pki\n  namespace: cp9-system\n" +
		"  labels:\n    app.kubernetes.io/part-of: cp9-dedicated-api\n" +
		"type: Opaque\ndata:\n")
	keys := make([]string, 0, len(secretData))
	for key := range secretData {
		keys = append(keys, key)
	}
	slices.Sort(keys)
	for _, key := range keys {
		fmt.Fprintf(secret, "  %s: %s\n", key, base64.StdEncoding.EncodeToString(secretData[key]))
	}

	resources := fmt.Sprintf(`---
apiVersion: v1
kind: Service
metadata:
  name: dedicated-etcd
  namespace: cp9-system
  labels:
    app.kubernetes.io/name: dedicated-etcd
    cp9-session: %s
spec:
  selector:
    app.kubernetes.io/name: dedicated-etcd
  ports:
  - name: client
    port: 2379
    targetPort: 2379
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: dedicated-etcd-data
  namespace: cp9-system
  labels:
    app.kubernetes.io/name: dedicated-etcd
    cp9-session: %s
spec:
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 2Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: dedicated-etcd
  namespace: cp9-system
  labels:
    app.kubernetes.io/name: dedicated-etcd
    cp9-session: %s
spec:
  serviceName: dedicated-etcd
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: dedicated-etcd
  template:
    metadata:
      labels:
        app.kubernetes.io/name: dedicated-etcd
        cp9-session: %s
    spec:
      automountServiceAccountToken: false
      containers:
      - name: etcd
        image: %s
        imagePullPolicy: IfNotPresent
        command: [/usr/local/bin/etcd]
        args:
        - --name=%s
        - --data-dir=/var/lib/etcd
        - --listen-client-urls=https://0.0.0.0:2379
        - --advertise-client-urls=https://dedicated-etcd.cp9-system.svc:2379
        - --client-cert-auth=true
        - --trusted-ca-file=/pki/etcd-ca.crt
        - --cert-file=/pki/etcd-server.crt
        - --key-file=/pki/etcd-server.key
        ports:
        - name: client
          containerPort: 2379
        readinessProbe:
          tcpSocket:
            port: client
          periodSeconds: 3
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            memory: 512Mi
        volumeMounts:
        - name: data
          mountPath: /var/lib/etcd
        - name: pki
          mountPath: /pki
          readOnly: true
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: dedicated-etcd-data
      - name: pki
        secret:
          secretName: dedicated-api-pki
          defaultMode: 0400
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dedicated-kube-apiserver
  namespace: cp9-system
  labels:
    app.kubernetes.io/name: dedicated-kube-apiserver
    cp9-session: %s
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: dedicated-kube-apiserver
  template:
    metadata:
      labels:
        app.kubernetes.io/name: dedicated-kube-apiserver
        cp9-session: %s
    spec:
      automountServiceAccountToken: false
      containers:
      - name: kube-apiserver
        image: %s
        imagePullPolicy: IfNotPresent
        command: [kube-apiserver]
        args:
        - --advertise-address=0.0.0.0
        - --secure-port=6443
        - --authorization-mode=RBAC
        - --client-ca-file=/pki/api-ca.crt
        - --tls-cert-file=/pki/apiserver.crt
        - --tls-private-key-file=/pki/apiserver.key
        - --etcd-servers=https://dedicated-etcd.cp9-system.svc:2379
        - --etcd-cafile=/pki/etcd-ca.crt
        - --etcd-certfile=/pki/etcd-client.crt
        - --etcd-keyfile=/pki/etcd-client.key
        - --service-account-issuer=https://dedicated-kube-apiserver.cp9-system.svc
        - --service-account-key-file=/pki/service-account.pub
        - --service-account-signing-key-file=/pki/service-account.key
        - --service-cluster-ip-range=10.96.0.0/24
        - --enable-admission-plugins=NamespaceLifecycle,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota
        ports:
        - name: https
          containerPort: 6443
        readinessProbe:
          tcpSocket:
            port: https
          periodSeconds: 3
        resources:
          requests:
            cpu: 200m
            memory: 512Mi
          limits:
            memory: 2Gi
        volumeMounts:
        - name: pki
          mountPath: /pki
          readOnly: true
      - name: kube-controller-manager
        image: %s
        imagePullPolicy: IfNotPresent
        command: [kube-controller-manager]
        args:
        - --kubeconfig=/pki/controller-manager.kubeconfig
        - --leader-elect=false
        - --controllers=namespace,serviceaccount-token,root-ca-cert-publisher,garbagecollector
        - --service-account-private-key-file=/pki/service-account.key
        - --root-ca-file=/pki/api-ca.crt
        - --use-service-account-credentials=false
        resources:
          requests:
            cpu: 50m
            memory: 64Mi
          limits:
            memory: 256Mi
        volumeMounts:
        - name: pki
          mountPath: /pki
          readOnly: true
      volumes:
      - name: pki
        secret:
          secretName: dedicated-api-pki
          defaultMode: 0400
---
apiVersion: v1
kind: Service
metadata:
  name: dedicated-kube-apiserver
  namespace: cp9-system
  labels:
    app.kubernetes.io/name: dedicated-kube-apiserver
    cp9-session: %s
spec:
  type: NodePort
  selector:
    app.kubernetes.io/name: dedicated-kube-apiserver
  ports:
  - name: https
    port: 6443
    targetPort: 6443
    nodePort: %d
`, selected.ClusterName, selected.ClusterName, selected.ClusterName, selected.ClusterName,
		state.EtcdImage, state.EtcdMemberID, selected.ClusterName, selected.ClusterName,
		state.APIServerImage, state.ControllerManagerImage, selected.ClusterName, state.NodePort)

	return append(secret.Bytes(), []byte(resources)...), nil
}

func controlPlaneSecretData(state *host.DedicatedAPISession) (map[string][]byte, error) {
	paths := map[string]string{
		"api-ca.crt":                    filepath.Join(state.PKIDir, "api-ca.crt"),
		"apiserver.crt":                 filepath.Join(state.PKIDir, "apiserver.crt"),
		"apiserver.key":                 filepath.Join(state.PKIDir, "apiserver.key"),
		"etcd-ca.crt":                   filepath.Join(state.PKIDir, "etcd-ca.crt"),
		"etcd-server.crt":               filepath.Join(state.PKIDir, "etcd-server.crt"),
		"etcd-server.key":               filepath.Join(state.PKIDir, "etcd-server.key"),
		"etcd-client.crt":               filepath.Join(state.PKIDir, "etcd-client.crt"),
		"etcd-client.key":               filepath.Join(state.PKIDir, "etcd-client.key"),
		"service-account.key":           filepath.Join(state.PKIDir, "service-account.key"),
		"service-account.pub":           filepath.Join(state.PKIDir, "service-account.pub"),
		"controller-manager.kubeconfig": ComponentKubeconfigPath(state, "controller-manager"),
	}
	data := make(map[string][]byte, len(paths))
	for key, path := range paths {
		contents, err := os.ReadFile(path)
		if err != nil {
			return nil, fmt.Errorf("read Dedicated API secret input %s: %w", path, err)
		}
		data[key] = contents
	}
	return data, nil
}

// RenderBootstrapManifest returns metadata-only policy and component RBAC for
// application through the Dedicated API admin kubeconfig.
func RenderBootstrapManifest(sessionName string) []byte {
	return fmt.Appendf(nil, `apiVersion: v1
kind: Namespace
metadata:
  name: cp9-dedicated
  labels:
    cp9-session: %s
---
apiVersion: v1
kind: Namespace
metadata:
  name: dedicated-system
  labels:
    cp9-session: %s
---
apiVersion: v1
kind: Namespace
metadata:
  name: ticdc-service-meta
  labels:
    cp9-session: %s
---
apiVersion: v1
kind: Namespace
metadata:
  name: observability
  labels:
    cp9-session: %s
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cp9-dedicated-garbage-collector
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:controller:generic-garbage-collector
subjects:
- kind: User
  name: system:kube-controller-manager
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cp9-dedicated-regional-server
rules:
- apiGroups: ["dedicated.regionalserver.tidbcloud.com", "agent.observability.pingcap.com"]
  resources: ["*"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["aws.crossplane.io", "compute.gcp.crossplane.io", "container.gcp.crossplane.io", "ec2.aws.crossplane.io", "eks.aws.crossplane.io", "gcp.crossplane.io", "iam.aws.crossplane.io", "iam.gcp.crossplane.io", "route53.aws.crossplane.io", "s3.aws.crossplane.io", "storage.gcp.crossplane.io", "vpcpeering.gcp.crossplane.io"]
  resources: ["*"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete", "deletecollection"]
- apiGroups: ["cloud.pingcap.com"]
  resources: ["imports", "imports/status", "imports/finalizers"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["eks.infra.pingcap.com"]
  resources: ["dmjobs", "dmjobs/status", "dmjobs/finalizers", "privatelinkendpoints", "privatelinkendpoints/status", "privatelinkendpoints/finalizers"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
  resources: ["configmaps", "secrets", "events", "namespaces"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["coordination.k8s.io"]
  resources: ["leases"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cp9-dedicated-regional-server
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cp9-dedicated-regional-server
subjects:
- kind: User
  name: cp9:dedicated:regional-server
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cp9-dedicated-cluster-control-server
rules:
- apiGroups: ["cloud.pingcap.com"]
  resources: ["clusters", "clusters/status", "configchanges", "configchanges/status", "configrevisions", "configrevisions/status", "workflows", "workflows/status", "backups", "backups/status", "restores", "restores/status", "compactbackups", "compactbackups/status", "replays", "replays/status", "upgradeoperations", "upgradeoperations/status"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["cloud.pingcap.com"]
  resources: ["backups/finalizers", "restores/finalizers", "compactbackups/finalizers", "replays/finalizers", "upgradeoperations/finalizers"]
  verbs: ["update"]
- apiGroups: [""]
  resources: ["configmaps", "secrets", "events", "namespaces"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["route53.aws.crossplane.io"]
  resources: ["resourcerecordsets"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["coordination.k8s.io"]
  resources: ["leases"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cp9-dedicated-cluster-control-server
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cp9-dedicated-cluster-control-server
subjects:
- kind: User
  name: cp9:dedicated:cluster-control-server
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cp9-dedicated-cluster-controller
rules:
- apiGroups: ["cloud.pingcap.com"]
  resources: ["clusters", "clusters/status", "configchanges", "configchanges/status", "configrevisions", "configrevisions/status", "imports", "imports/status", "imports/finalizers", "networks", "networks/status", "networks/finalizers", "workflows", "workflows/status", "backups", "backups/status", "backups/finalizers", "restores", "restores/status", "restores/finalizers"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["cloud.pingcap.com"]
  resources: ["compactbackups", "replays", "upgradeoperations"]
  verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
- apiGroups: ["cloud.pingcap.com"]
  resources: ["compactbackups/status", "replays/status", "upgradeoperations/status"]
  verbs: ["get", "patch", "update"]
- apiGroups: ["cloud.pingcap.com"]
  resources: ["compactbackups/finalizers", "replays/finalizers", "upgradeoperations/finalizers"]
  verbs: ["update"]
- apiGroups: ["aws.crossplane.io", "autoscaling.aws.crossplane.io", "compute.gcp.crossplane.io", "container.gcp.crossplane.io", "dns.gcp.crossplane.io", "ec2.aws.crossplane.io", "eks.aws.crossplane.io", "gcp.crossplane.io", "iam.aws.crossplane.io", "iam.gcp.crossplane.io", "kms.aws.crossplane.io", "kms.gcp.crossplane.io", "pubsub.gcp.crossplane.io", "route53.aws.crossplane.io", "s3.aws.crossplane.io", "storage.gcp.crossplane.io"]
  resources: ["*"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete", "deletecollection"]
- apiGroups: ["eks.infra.pingcap.com"]
  resources: ["dmjobs", "dmjobs/status", "dmjobs/finalizers", "dmproviders", "privatelinkendpoints", "privatelinkendpoints/status", "privatelinkendpoints/finalizers"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
  resources: ["configmaps", "secrets", "events", "namespaces", "services"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["networking.k8s.io"]
  resources: ["ingresses"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["coordination.k8s.io"]
  resources: ["leases"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cp9-dedicated-cluster-controller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cp9-dedicated-cluster-controller
subjects:
- kind: User
  name: cp9:dedicated:cluster-controller
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cp9-dedicated-classic-provider
rules:
- apiGroups: ["acm.aws.crossplane.io", "acmpca.aws.crossplane.io", "apigateway.aws.crossplane.io", "apigatewayv2.aws.crossplane.io", "athena.aws.crossplane.io", "autoscaling.aws.crossplane.io", "aws.crossplane.io", "batch.aws.crossplane.io", "cache.aws.crossplane.io", "cloudfront.aws.crossplane.io", "cloudsearch.aws.crossplane.io", "cloudwatchlogs.aws.crossplane.io", "cognitoidentity.aws.crossplane.io", "cognitoidentityprovider.aws.crossplane.io", "database.aws.crossplane.io", "dax.aws.crossplane.io", "docdb.aws.crossplane.io", "dynamodb.aws.crossplane.io", "ec2.aws.crossplane.io", "ecr.aws.crossplane.io", "ecs.aws.crossplane.io", "efs.aws.crossplane.io", "eks.aws.crossplane.io", "elasticache.aws.crossplane.io", "elasticloadbalancing.aws.crossplane.io", "elbv2.aws.crossplane.io", "emrcontainers.aws.crossplane.io", "firehose.aws.crossplane.io", "globalaccelerator.aws.crossplane.io", "glue.aws.crossplane.io", "iam.aws.crossplane.io", "iot.aws.crossplane.io", "kafka.aws.crossplane.io", "kinesis.aws.crossplane.io", "kms.aws.crossplane.io", "lambda.aws.crossplane.io", "mq.aws.crossplane.io", "mwaa.aws.crossplane.io", "neptune.aws.crossplane.io", "opensearchservice.aws.crossplane.io", "prometheusservice.aws.crossplane.io", "ram.aws.crossplane.io", "rds.aws.crossplane.io", "redshift.aws.crossplane.io", "route53.aws.crossplane.io", "route53resolver.aws.crossplane.io", "s3.aws.crossplane.io", "s3control.aws.crossplane.io", "secretsmanager.aws.crossplane.io", "servicecatalog.aws.crossplane.io", "servicediscovery.aws.crossplane.io", "sesv2.aws.crossplane.io", "sfn.aws.crossplane.io", "sns.aws.crossplane.io", "sqs.aws.crossplane.io", "transfer.aws.crossplane.io"]
  resources: ["*"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["cache.gcp.crossplane.io", "compute.gcp.crossplane.io", "container.gcp.crossplane.io", "database.gcp.crossplane.io", "dns.gcp.crossplane.io", "gcp.crossplane.io", "iam.gcp.crossplane.io", "kms.gcp.crossplane.io", "pubsub.gcp.crossplane.io", "registry.gcp.crossplane.io", "servicenetworking.gcp.crossplane.io", "storage.gcp.crossplane.io", "vpcpeering.gcp.crossplane.io"]
  resources: ["*"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
  resources: ["secrets", "events"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["coordination.k8s.io"]
  resources: ["leases"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["aws.upbound.io", "eks.aws.upbound.io", "cloudplatform.gcp.upbound.io", "compute.gcp.upbound.io", "container.gcp.upbound.io", "dns.gcp.upbound.io", "gcp.upbound.io", "iam.gcp.upbound.io", "storage.gcp.upbound.io"]
  resources: ["*"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cp9-dedicated-classic-provider
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cp9-dedicated-classic-provider
subjects:
- kind: User
  name: cp9:dedicated:classic-provider
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cp9-dedicated-ticdc-service
rules:
- apiGroups: ["cdc.tidbcloud.com"]
  resources: ["*"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
  resources: ["configmaps", "secrets", "events", "namespaces"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["coordination.k8s.io"]
  resources: ["leases"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["cloud.pingcap.com"]
  resources: ["clusters"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["aws.upbound.io", "eks.aws.upbound.io"]
  resources: ["*"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cp9-dedicated-ticdc-service
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cp9-dedicated-ticdc-service
subjects:
- kind: User
  name: cp9:dedicated:ticdc-service
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cp9-dedicated-readiness
rules:
- apiGroups: ["*"]
  resources: ["*"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cp9-dedicated-readiness
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cp9-dedicated-readiness
subjects:
- kind: User
  name: cp9:dedicated:readiness
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: cp9-dedicated-metadata-only
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
    - apiGroups: [""]
      apiVersions: ["v1"]
      operations: ["CREATE", "UPDATE"]
      resources: ["pods"]
    - apiGroups: ["batch"]
      apiVersions: ["v1"]
      operations: ["CREATE", "UPDATE"]
      resources: ["jobs"]
    - apiGroups: ["apps"]
      apiVersions: ["v1"]
      operations: ["CREATE", "UPDATE"]
      resources: ["deployments", "statefulsets", "daemonsets"]
  validations:
  - expression: "false"
    message: "metadata-only Dedicated API rejects executable workload objects"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: cp9-dedicated-metadata-only
spec:
  policyName: cp9-dedicated-metadata-only
  validationActions: [Deny]
`, sessionName, sessionName, sessionName, sessionName)
}
