package svc

import (
	"context"
	"database/sql"
	"log"
	"net/url"
	"strings"
	"time"

	"aijianzhan/platform/internal/agentstore"
	"aijianzhan/platform/internal/audit"
	"aijianzhan/platform/internal/authx"
	"aijianzhan/platform/internal/bindcodestore"
	"aijianzhan/platform/internal/config"
	"aijianzhan/platform/internal/crud"
	"aijianzhan/platform/internal/dbsync"
	"aijianzhan/platform/internal/invitestore"
	"aijianzhan/platform/internal/license"
	"aijianzhan/platform/internal/meta"
	"aijianzhan/platform/internal/orgunitstore"
	"aijianzhan/platform/internal/ratelimit"
	"aijianzhan/platform/internal/rolestore"
	"aijianzhan/platform/internal/schema"
	"aijianzhan/platform/internal/smsstore"
	"aijianzhan/platform/internal/storage"
	"aijianzhan/platform/internal/tenantperm"
	"aijianzhan/platform/internal/userstore"
	"aijianzhan/platform/internal/yuhticket"

	_ "github.com/lib/pq"
)

type ServiceContext struct {
	Config     config.Config
	Meta       meta.Store
	Schema     schema.Runner
	CRUD       crud.Engine
	Users      userstore.Store
	Agents     agentstore.Store
	Roles      rolestore.Store
	Invites    invitestore.Store
	OrgUnits   orgunitstore.Store
	Audit      audit.Store
	Objects    storage.Store
	Limiter    *ratelimit.Limiter
	DBPool     *crud.DBPool
	MemoryMode bool
	DB         *sql.DB
	JWT        authx.JWTConfig
	DBSync     *dbsync.Manager
	BindCodes  bindcodestore.Store
	YuhengJTI  *yuhticket.JTIStore
	TenantPerm tenantperm.Store
	SMS        *smsstore.Store
	License    *license.Manager
}

func NewServiceContext(c config.Config) *ServiceContext {
	users := userstore.NewMemoryStore()
	aud := audit.NewMemoryStore()
	agents := agentstore.NewMemoryStore()
	roles := rolestore.NewMemoryStore()
	invites := invitestore.NewMemoryStore()
	orgUnits := orgunitstore.NewMemoryStore()
	pubBase := strings.TrimRight(c.PublicBaseURL, "/")
	if pubBase == "" {
		pubBase = "http://127.0.0.1:8180"
	}
	storageBase := c.Storage.PublicBase
	if storageBase == "" {
		storageBase = pubBase + "/api/v1/storage"
	}
	obj, err := storage.NewLocalStore(c.Storage.LocalRoot, storageBase)
	if err != nil {
		log.Printf("storage init: %v", err)
		obj, _ = storage.NewLocalStore("./data/uploads", storageBase)
	}

	ctx := &ServiceContext{
		Config:     c,
		Meta:       meta.NewMemoryStore(),
		Schema:     schema.NoopRunner{},
		CRUD:       crud.NewMemoryEngine(),
		Users:      users,
		Agents:     agents,
		Roles:      roles,
		Invites:    invites,
		OrgUnits:   orgUnits,
		Audit:      aud,
		TenantPerm: tenantperm.NewMemoryStore(),
		Objects:    obj,
		Limiter:    ratelimit.New(c.RateLimitPerMin),
		MemoryMode: true,
		SMS: smsstore.New(
			time.Duration(c.SMS.CodeTTLSeconds)*time.Second,
			time.Duration(c.SMS.ResendSeconds)*time.Second,
			c.SMS.DevFixedCode,
		),
		JWT: authx.JWTConfig{
			AccessSecret: c.Auth.AccessSecret,
			AccessExpire: c.Auth.AccessExpire,
		},
	}
	if ctx.JWT.AccessSecret == "" {
		ctx.JWT.AccessSecret = "dev-only-change-me"
	}

	if c.DataSource != "" && !c.DryRun {
		db, err := sql.Open("postgres", c.DataSource)
		if err != nil {
			log.Printf("postgres open failed, fallback memory: %v", err)
		} else if err := db.Ping(); err != nil {
			log.Printf("postgres ping failed, fallback memory: %v", err)
			_ = db.Close()
		} else {
			db.SetMaxOpenConns(20)
			db.SetMaxIdleConns(5)
			migCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
			defer cancel()
			if err := meta.EnsureSchema(migCtx, db); err != nil {
				log.Printf("meta migrate failed: %v", err)
				ctx.DB = db
				ctx.Schema = &schema.PostgresRunner{DB: db, AdminDSN: adminDSN(c.DataSource)}
				pool := crud.NewDBPool(db, c.DataSource)
				ctx.DBPool = pool
				ctx.CRUD = crud.NewPostgresEngine(db, pool)
				ctx.MemoryMode = false
			} else {
				ctx.DB = db
				ctx.Schema = &schema.PostgresRunner{DB: db, AdminDSN: adminDSN(c.DataSource)}
				pool := crud.NewDBPool(db, c.DataSource)
				ctx.DBPool = pool
				ctx.CRUD = crud.NewPostgresEngine(db, pool)
				ctx.Meta = meta.NewPostgresStore(db)
				ctx.Users = userstore.NewPostgresStore(db)
				ctx.Agents = agentstore.NewPostgresStore(db)
				ctx.Roles = rolestore.NewPostgresStore(db)
				ctx.Invites = invitestore.NewPostgresStore(db)
				ctx.OrgUnits = orgunitstore.NewPostgresStore(db)
				ctx.Audit = audit.NewPostgresStore(db)
				ctx.TenantPerm = tenantperm.NewPostgresStore(db)
				ctx.MemoryMode = false
				log.Printf("postgres engine + meta + users + agents + roles + invites + org_units + audit + tenant_perm enabled")
			}
		}
	}
	userstore.EnsureDemoUser(context.Background(), ctx.Users)
	userstore.EnsurePlatformAdminUser(context.Background(), ctx.Users)
	if ctx.Users != nil {
		if err := ctx.Users.EnsureTenantSlugs(context.Background()); err != nil {
			log.Printf("ensure tenant slugs: %v", err)
		}
	}
	_ = ctx.Roles.EnsureDefaults(context.Background(), 1)
	if ctx.TenantPerm != nil {
		_ = ctx.TenantPerm.EnsureDefault(context.Background(), 1)
		authx.EntitlementChecker = func(c context.Context, tenantID int64, perm string) bool {
			ok, err := ctx.TenantPerm.Allows(c, tenantID, perm)
			if err != nil {
				log.Printf("entitlement check: %v", err)
				return false
			}
			return ok
		}
	}
	// Z9e：存量已发布模块补齐 import/export（不依赖用户再发布）
	if ctx.Meta != nil && !ctx.MemoryMode {
		bfCtx, bfCancel := context.WithTimeout(context.Background(), 30*time.Second)
		meta.RunBackfillDefaultImportExportOnBoot(bfCtx, ctx.Meta)
		bfCancel()
	}
	if c.DryRun {
		ctx.Schema = schema.NoopRunner{}
	}

	// 跨库同步中间件（默认启用）
	if c.DBSync.Enabled {
		dir := c.DBSync.DataDir
		if dir == "" {
			dir = "./data/dbsync"
		}
		store, err := dbsync.NewFileStore(dir)
		if err != nil {
			log.Printf("dbsync store: %v", err)
		} else {
			ctx.DBSync = dbsync.NewManager(store)
			log.Printf("dbsync middleware enabled (dir=%s)", dir)
			if bc, err := bindcodestore.NewFileStore(dir); err != nil {
				log.Printf("bind code store: %v", err)
			} else {
				ctx.BindCodes = bc
			}
			if jti, err := yuhticket.NewJTIStore(dir); err != nil {
				log.Printf("yuheng ticket jti: %v", err)
			} else {
				ctx.YuhengJTI = jti
			}
		}
	}

	licCfg := c.License
	if licCfg.Enabled {
		if strings.TrimSpace(licCfg.ControlSecret) == "" {
			licCfg.ControlSecret = c.Auth.IssueSecret
		}
		if strings.TrimSpace(licCfg.SignSecret) == "" {
			licCfg.SignSecret = licCfg.ControlSecret
		}
	}
	lic, err := license.NewManager(licCfg)
	if err != nil {
		log.Printf("license manager: %v", err)
	} else {
		ctx.License = lic
		if lic.Enabled() {
			if ctx.DB != nil {
				lic.AttachMirror(license.NewPGMirror(ctx.DB))
			}
			log.Printf("license lease enabled (leases=%s state=%s)", lic.Path(), lic.StatePath())
		}
	}
	return ctx
}

func adminDSN(dsn string) string {
	u, err := url.Parse(dsn)
	if err != nil {
		return dsn
	}
	u.Path = "/postgres"
	return u.String()
}
