package store

import (
	"crypto/rand"
	"crypto/sha256"
	"database/sql"
	"encoding/hex"
	"errors"
	"fmt"
	"net"
	"strings"
	"time"

	"golang.org/x/crypto/bcrypt"
	_ "modernc.org/sqlite"
)

type Store struct {
	DB *sql.DB
}

type Tenant struct {
	ID        int64
	Name      string
	Status    int
	Subnet    string
	CreatedAt int64
}

type User struct {
	ID           int64
	TenantID     int64
	Role         string
	Email        string
	PasswordHash string
	Status       int
	CreatedAt    int64
}

type APIKey struct {
	ID        int64
	TenantID  int64
	Hash      string
	Scope     string
	ExpiresAt *int64
	Status    int
	CreatedAt int64
	Plain     string
}

type NodeCredential struct {
	NodeID    int64
	NodeUUID  string
	NodeName  string
	Alias     string
	Secret    string
	VirtualIP string
	TenantID  int64
	Status    int
	CreatedAt int64
	LastSeen  *int64
}

type EnrollToken struct {
	ID         int64
	TenantID   int64
	Hash       string
	ExpiresAt  int64
	UsedAt     *int64
	MaxAttempt int
	Attempts   int
	Status     int
	CreatedAt  int64
}

type SessionToken struct {
	ID        int64
	UserID    int64
	TenantID  int64
	Role      string
	TokenHash string
	ExpiresAt int64
	Status    int
	CreatedAt int64
}

func Open(dbPath string) (*Store, error) {
	db, err := sql.Open("sqlite", dbPath)
	if err != nil {
		return nil, err
	}
	if _, err := db.Exec(`PRAGMA journal_mode=WAL;`); err != nil {
		return nil, err
	}
	if _, err := db.Exec(`PRAGMA foreign_keys=ON;`); err != nil {
		return nil, err
	}
	s := &Store{DB: db}
	if err := s.migrate(); err != nil {
		return nil, err
	}
	if err := s.ensureSubnetPool(); err != nil {
		return nil, err
	}
	return s, nil
}

func (s *Store) migrate() error {
	stmts := []string{
		`CREATE TABLE IF NOT EXISTS tenants (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			name TEXT NOT NULL UNIQUE,
			status INTEGER NOT NULL DEFAULT 1,
			subnet TEXT NOT NULL UNIQUE,
			created_at INTEGER NOT NULL
		);`,
		`CREATE TABLE IF NOT EXISTS users (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			tenant_id INTEGER NOT NULL,
			role TEXT NOT NULL,
			email TEXT,
			password_hash TEXT,
			status INTEGER NOT NULL DEFAULT 1,
			created_at INTEGER NOT NULL,
			FOREIGN KEY(tenant_id) REFERENCES tenants(id)
		);`,
		`CREATE TABLE IF NOT EXISTS api_keys (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			tenant_id INTEGER NOT NULL,
			key_hash TEXT NOT NULL UNIQUE,
			scope TEXT,
			expires_at INTEGER,
			status INTEGER NOT NULL DEFAULT 1,
			created_at INTEGER NOT NULL,
			FOREIGN KEY(tenant_id) REFERENCES tenants(id)
		);`,
		`CREATE TABLE IF NOT EXISTS nodes (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			tenant_id INTEGER NOT NULL,
			node_uuid TEXT,
			node_name TEXT NOT NULL,
			alias TEXT DEFAULT '',
			node_pubkey TEXT,
			node_secret_hash TEXT,
			virtual_ip TEXT,
			status INTEGER NOT NULL DEFAULT 1,
			last_seen INTEGER,
			created_at INTEGER NOT NULL DEFAULT 0,
			FOREIGN KEY(tenant_id) REFERENCES tenants(id)
		);`,
		`CREATE TABLE IF NOT EXISTS enroll_tokens (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			tenant_id INTEGER NOT NULL,
			token_hash TEXT NOT NULL UNIQUE,
			expires_at INTEGER NOT NULL,
			used_at INTEGER,
			max_attempt INTEGER NOT NULL DEFAULT 5,
			attempts INTEGER NOT NULL DEFAULT 0,
			status INTEGER NOT NULL DEFAULT 1,
			created_at INTEGER NOT NULL,
			FOREIGN KEY(tenant_id) REFERENCES tenants(id)
		);`,
		`CREATE TABLE IF NOT EXISTS peering_policies (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			src_tenant_id INTEGER NOT NULL,
			dst_tenant_id INTEGER NOT NULL,
			rules TEXT,
			expires_at INTEGER,
			status INTEGER NOT NULL DEFAULT 1,
			created_at INTEGER NOT NULL,
			FOREIGN KEY(src_tenant_id) REFERENCES tenants(id),
			FOREIGN KEY(dst_tenant_id) REFERENCES tenants(id)
		);`,
		`CREATE TABLE IF NOT EXISTS audit_logs (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			actor_type TEXT,
			actor_id TEXT,
			action TEXT,
			target_type TEXT,
			target_id TEXT,
			detail TEXT,
			ip TEXT,
			created_at INTEGER NOT NULL
		);`,
		`CREATE TABLE IF NOT EXISTS subnet_pool (
			subnet TEXT PRIMARY KEY,
			status INTEGER NOT NULL DEFAULT 0,
			reserved INTEGER NOT NULL DEFAULT 0,
			tenant_id INTEGER,
			updated_at INTEGER NOT NULL
		);`,
		`CREATE TABLE IF NOT EXISTS session_tokens (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			user_id INTEGER NOT NULL,
			tenant_id INTEGER NOT NULL,
			role TEXT NOT NULL,
			token_hash TEXT NOT NULL UNIQUE,
			expires_at INTEGER NOT NULL,
			status INTEGER NOT NULL DEFAULT 1,
			created_at INTEGER NOT NULL,
			FOREIGN KEY(user_id) REFERENCES users(id),
			FOREIGN KEY(tenant_id) REFERENCES tenants(id)
		);`,
	}
	for _, stmt := range stmts {
		if _, err := s.DB.Exec(stmt); err != nil {
			return err
		}
	}
	if _, err := s.DB.Exec(`ALTER TABLE nodes ADD COLUMN node_uuid TEXT`); err != nil && !strings.Contains(err.Error(), "duplicate column name") {
		return err
	}
	if _, err := s.DB.Exec(`ALTER TABLE nodes ADD COLUMN alias TEXT DEFAULT ''`); err != nil && !strings.Contains(err.Error(), "duplicate column name") {
		return err
	}
	if _, err := s.DB.Exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_nodes_node_uuid ON nodes(node_uuid)`); err != nil {
		return err
	}
	return nil
}

func (s *Store) ensureSubnetPool() error {
	// pool: 10.10.1.0/24 .. 10.10.254.0/24
	// reserve: 10.10.0.0/24 and 10.10.255.0/24
	rows, err := s.DB.Query(`SELECT COUNT(1) FROM subnet_pool;`)
	if err != nil {
		return err
	}
	defer rows.Close()
	var count int
	if rows.Next() {
		_ = rows.Scan(&count)
	}
	if count > 0 {
		return nil
	}
	now := time.Now().Unix()
	insert := `INSERT INTO subnet_pool(subnet,status,reserved,tenant_id,updated_at) VALUES(?,?,?,?,?)`
	// reserved
	_, _ = s.DB.Exec(insert, "10.10.0.0/24", 0, 1, nil, now)
	_, _ = s.DB.Exec(insert, "10.10.255.0/24", 0, 1, nil, now)
	for i := 1; i <= 254; i++ {
		sn := fmt.Sprintf("10.10.%d.0/24", i)
		_, _ = s.DB.Exec(insert, sn, 0, 0, nil, now)
	}
	return nil
}

func (s *Store) AllocateSubnet() (string, error) {
	// find first available subnet
	row := s.DB.QueryRow(`SELECT subnet FROM subnet_pool WHERE status=0 AND reserved=0 ORDER BY subnet LIMIT 1`)
	var subnet string
	if err := row.Scan(&subnet); err != nil {
		return "", err
	}
	if subnet == "" {
		return "", errors.New("no subnet available")
	}
	return subnet, nil
}

func (s *Store) CreateTenant(name string) (*Tenant, error) {
	sn, err := s.AllocateSubnet()
	if err != nil {
		return nil, err
	}
	now := time.Now().Unix()
	res, err := s.DB.Exec(`INSERT INTO tenants(name,status,subnet,created_at) VALUES(?,?,?,?)`, name, 1, sn, now)
	if err != nil {
		return nil, err
	}
	id, _ := res.LastInsertId()
	_, _ = s.DB.Exec(`UPDATE subnet_pool SET status=1, tenant_id=?, updated_at=? WHERE subnet=?`, id, now, sn)
	return &Tenant{ID: id, Name: name, Status: 1, Subnet: sn, CreatedAt: now}, nil
}

func (s *Store) CreateTenantWithUsers(name, adminPassword, operatorPassword string) (*Tenant, *User, *User, error) {
	if adminPassword == "" || operatorPassword == "" {
		return nil, nil, nil, errors.New("password required")
	}
	ten, err := s.CreateTenant(name)
	if err != nil {
		return nil, nil, nil, err
	}
	admin, err := s.CreateUser(ten.ID, "admin", "admin@local", adminPassword, 1)
	if err != nil {
		return nil, nil, nil, err
	}
	op, err := s.CreateUser(ten.ID, "operator", "operator@local", operatorPassword, 1)
	if err != nil {
		return nil, nil, nil, err
	}
	return ten, admin, op, nil
}

func (s *Store) AllocateNodeIP(tenantID int64) (string, error) {
	ten, err := s.GetTenantByID(tenantID)
	if err != nil || ten == nil {
		return "", errors.New("tenant not found")
	}
	ip, ipnet, err := net.ParseCIDR(ten.Subnet)
	if err != nil {
		return "", err
	}
	base := ip.To4()
	if base == nil {
		return "", errors.New("only ipv4 subnet supported")
	}
	rows, err := s.DB.Query(`SELECT virtual_ip FROM nodes WHERE tenant_id=? AND virtual_ip IS NOT NULL AND virtual_ip<>''`, tenantID)
	if err != nil {
		return "", err
	}
	defer rows.Close()
	used := map[string]bool{}
	for rows.Next() {
		var v string
		if rows.Scan(&v) == nil && v != "" {
			used[v] = true
		}
	}
	for host := 2; host <= 254; host++ {
		cand := net.IPv4(base[0], base[1], base[2], byte(host)).String()
		if !ipnet.Contains(net.ParseIP(cand)) {
			continue
		}
		if used[cand] {
			continue
		}
		return cand, nil
	}
	return "", errors.New("no available ip")
}

func (s *Store) CreateNodeCredential(tenantID int64, nodeName string) (*NodeCredential, error) {
	secret := randToken()
	h := hashTokenString(secret)
	now := time.Now().Unix()
	nodeUUID := randUUID()
	vip, err := s.AllocateNodeIP(tenantID)
	if err != nil {
		return nil, err
	}
	res, err := s.DB.Exec(`INSERT INTO nodes(tenant_id,node_uuid,node_name,alias,node_secret_hash,virtual_ip,status,last_seen,created_at) VALUES(?,?,?,?,?,?,1,?,?)`, tenantID, nodeUUID, nodeName, "", h, vip, now, now)
	if err != nil {
		return nil, err
	}
	id, _ := res.LastInsertId()
	return &NodeCredential{NodeID: id, NodeUUID: nodeUUID, NodeName: nodeName, Alias: "", Secret: secret, VirtualIP: vip, TenantID: tenantID, Status: 1, CreatedAt: now, LastSeen: &now}, nil
}

func (s *Store) VerifyNodeSecret(nodeName, secret string) (*Tenant, error) {
	h := hashTokenString(secret)
	row := s.DB.QueryRow(`SELECT t.id,t.name,t.status,t.subnet,t.created_at FROM nodes n JOIN tenants t ON n.tenant_id=t.id WHERE n.node_name=? AND n.node_secret_hash=? AND n.status=1 AND t.status=1`, nodeName, h)
	var t Tenant
	if err := row.Scan(&t.ID, &t.Name, &t.Status, &t.Subnet, &t.CreatedAt); err != nil {
		return nil, err
	}
	return &t, nil
}

func (s *Store) GetNodeCredentialByName(tenantID int64, nodeName string) (*NodeCredential, error) {
	row := s.DB.QueryRow(`SELECT id,node_uuid,node_name,alias,virtual_ip,tenant_id,status,last_seen,created_at FROM nodes WHERE tenant_id=? AND node_name=? ORDER BY id DESC LIMIT 1`, tenantID, nodeName)
	var n NodeCredential
	var seen sql.NullInt64
	if err := row.Scan(&n.NodeID, &n.NodeUUID, &n.NodeName, &n.Alias, &n.VirtualIP, &n.TenantID, &n.Status, &seen, &n.CreatedAt); err != nil {
		return nil, err
	}
	if seen.Valid {
		v := seen.Int64
		n.LastSeen = &v
	}
	return &n, nil
}

func (s *Store) SetNodeAlias(tenantID int64, nodeUUID, alias string) error {
	alias = strings.TrimSpace(alias)
	res, err := s.DB.Exec(`UPDATE nodes SET alias=? WHERE tenant_id=? AND node_uuid=?`, alias, tenantID, nodeUUID)
	if err != nil {
		return err
	}
	n, _ := res.RowsAffected()
	if n == 0 {
		return errors.New("node not found")
	}
	return nil
}

func (s *Store) SetNodeVirtualIP(tenantID int64, nodeUUID, ip string) error {
	ten, err := s.GetTenantByID(tenantID)
	if err != nil || ten == nil {
		return errors.New("tenant not found")
	}
	if net.ParseIP(ip) == nil {
		return errors.New("invalid ip")
	}
	_, ipnet, err := net.ParseCIDR(ten.Subnet)
	if err != nil {
		return err
	}
	if !ipnet.Contains(net.ParseIP(ip)) {
		return errors.New("ip not in subnet")
	}
	var c int
	if err := s.DB.QueryRow(`SELECT COUNT(1) FROM nodes WHERE tenant_id=? AND virtual_ip=? AND node_uuid<>?`, tenantID, ip, nodeUUID).Scan(&c); err != nil {
		return err
	}
	if c > 0 {
		return errors.New("ip conflict")
	}
	res, err := s.DB.Exec(`UPDATE nodes SET virtual_ip=? WHERE tenant_id=? AND node_uuid=?`, ip, tenantID, nodeUUID)
	if err != nil {
		return err
	}
	n, _ := res.RowsAffected()
	if n == 0 {
		return errors.New("node not found")
	}
	return nil
}

func (s *Store) GetTenantByToken(token uint64) (*Tenant, error) {
	h := hashToken(token)
	row := s.DB.QueryRow(`SELECT t.id,t.name,t.status,t.subnet,t.created_at FROM api_keys k JOIN tenants t ON k.tenant_id=t.id WHERE k.key_hash=? AND k.status=1 AND t.status=1`, h)
	var t Tenant
	if err := row.Scan(&t.ID, &t.Name, &t.Status, &t.Subnet, &t.CreatedAt); err != nil {
		return nil, err
	}
	return &t, nil
}

func (s *Store) GetTenantByID(id int64) (*Tenant, error) {
	row := s.DB.QueryRow(`SELECT id,name,status,subnet,created_at FROM tenants WHERE id=?`, id)
	var t Tenant
	if err := row.Scan(&t.ID, &t.Name, &t.Status, &t.Subnet, &t.CreatedAt); err != nil {
		return nil, err
	}
	return &t, nil
}

func (s *Store) CreateAPIKey(tenantID int64, scope string, ttl time.Duration) (string, error) {
	token := randToken()
	h := hashTokenString(token)
	now := time.Now().Unix()
	if ttl > 0 {
		e := time.Now().Add(ttl)
		_, err := s.DB.Exec(`INSERT INTO api_keys(tenant_id,key_hash,scope,expires_at,status,created_at) VALUES(?,?,?,?,1,?)`, tenantID, h, scope, e.Unix(), now)
		return token, err
	}
	_, err := s.DB.Exec(`INSERT INTO api_keys(tenant_id,key_hash,scope,expires_at,status,created_at) VALUES(?,?,?,?,1,?)`, tenantID, h, scope, nil, now)
	return token, err
}

func (s *Store) CreateEnrollToken(tenantID int64, ttl time.Duration, maxAttempt int) (string, error) {
	code := randToken()
	h := hashTokenString(code)
	exp := time.Now().Add(ttl).Unix()
	now := time.Now().Unix()
	_, err := s.DB.Exec(`INSERT INTO enroll_tokens(tenant_id,token_hash,expires_at,max_attempt,attempts,status,created_at) VALUES(?,?,?,?,0,1,?)`, tenantID, h, exp, maxAttempt, now)
	return code, err
}

func (s *Store) ConsumeEnrollToken(code string) (*EnrollToken, error) {
	h := hashTokenString(code)
	now := time.Now().Unix()
	row := s.DB.QueryRow(`SELECT id,tenant_id,expires_at,used_at,max_attempt,attempts,status,created_at FROM enroll_tokens WHERE token_hash=?`, h)
	var et EnrollToken
	var used sql.NullInt64
	if err := row.Scan(&et.ID, &et.TenantID, &et.ExpiresAt, &used, &et.MaxAttempt, &et.Attempts, &et.Status, &et.CreatedAt); err != nil {
		return nil, err
	}
	if used.Valid {
		return nil, errors.New("token already used")
	}
	if et.Status != 1 {
		return nil, errors.New("token disabled")
	}
	if et.Attempts >= et.MaxAttempt {
		return nil, errors.New("token attempts exceeded")
	}
	if now > et.ExpiresAt {
		return nil, errors.New("token expired")
	}
	// mark used
	_, err := s.DB.Exec(`UPDATE enroll_tokens SET used_at=?, attempts=attempts+1 WHERE id=?`, now, et.ID)
	if err != nil {
		return nil, err
	}
	et.UsedAt = &now
	return &et, nil
}

func (s *Store) IncEnrollAttempt(code string) {
	h := hashTokenString(code)
	_, _ = s.DB.Exec(`UPDATE enroll_tokens SET attempts=attempts+1 WHERE token_hash=?`, h)
}

// ListTenants returns all tenants (admin)
func (s *Store) ListTenants() ([]Tenant, error) {
	rows, err := s.DB.Query(`SELECT id,name,status,subnet,created_at FROM tenants ORDER BY id DESC`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var out []Tenant
	for rows.Next() {
		var t Tenant
		if err := rows.Scan(&t.ID, &t.Name, &t.Status, &t.Subnet, &t.CreatedAt); err == nil {
			out = append(out, t)
		}
	}
	return out, nil
}

func (s *Store) UpdateTenantStatus(id int64, status int) error {
	_, err := s.DB.Exec(`UPDATE tenants SET status=? WHERE id=?`, status, id)
	return err
}

func (s *Store) DeleteTenant(id int64) error {
	_, err := s.DB.Exec(`DELETE FROM tenants WHERE id=?`, id)
	return err
}

// ListAPIKeys returns api keys of a tenant (admin)
func (s *Store) ListAPIKeys(tenantID int64) ([]APIKey, error) {
	rows, err := s.DB.Query(`SELECT id,tenant_id,key_hash,scope,expires_at,status,created_at FROM api_keys WHERE tenant_id=? ORDER BY id DESC`, tenantID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var out []APIKey
	for rows.Next() {
		var k APIKey
		var exp sql.NullInt64
		if err := rows.Scan(&k.ID, &k.TenantID, &k.Hash, &k.Scope, &exp, &k.Status, &k.CreatedAt); err == nil {
			if exp.Valid {
				v := exp.Int64
				k.ExpiresAt = &v
			}
			out = append(out, k)
		}
	}
	return out, nil
}

func (s *Store) UpdateAPIKeyStatus(id int64, status int) error {
	_, err := s.DB.Exec(`UPDATE api_keys SET status=? WHERE id=?`, status, id)
	return err
}

func (s *Store) DeleteAPIKey(id int64) error {
	_, err := s.DB.Exec(`DELETE FROM api_keys WHERE id=?`, id)
	return err
}

// ListEnrollTokens returns enroll tokens for a tenant (admin)
func (s *Store) ListEnrollTokens(tenantID int64) ([]EnrollToken, error) {
	rows, err := s.DB.Query(`SELECT id,tenant_id,token_hash,expires_at,used_at,max_attempt,attempts,status,created_at FROM enroll_tokens WHERE tenant_id=? ORDER BY id DESC`, tenantID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var out []EnrollToken
	for rows.Next() {
		var et EnrollToken
		var used sql.NullInt64
		if err := rows.Scan(&et.ID, &et.TenantID, &et.Hash, &et.ExpiresAt, &used, &et.MaxAttempt, &et.Attempts, &et.Status, &et.CreatedAt); err == nil {
			if used.Valid {
				v := used.Int64
				et.UsedAt = &v
			}
			out = append(out, et)
		}
	}
	return out, nil
}

func (s *Store) UpdateEnrollStatus(id int64, status int) error {
	_, err := s.DB.Exec(`UPDATE enroll_tokens SET status=? WHERE id=?`, status, id)
	return err
}

func (s *Store) DeleteEnrollToken(id int64) error {
	_, err := s.DB.Exec(`DELETE FROM enroll_tokens WHERE id=?`, id)
	return err
}

func hashToken(token uint64) string {
	b := make([]byte, 8)
	for i := uint(0); i < 8; i++ {
		b[7-i] = byte(token >> (i * 8))
	}
	return hashTokenBytes(b)
}

func hashTokenString(token string) string {
	return hashTokenBytes([]byte(token))
}

func (s *Store) VerifyAPIKey(token string) (*Tenant, error) {
	h := hashTokenString(token)
	row := s.DB.QueryRow(`SELECT t.id,t.name,t.status,t.subnet,t.created_at FROM api_keys k JOIN tenants t ON k.tenant_id=t.id WHERE k.key_hash=? AND k.status=1 AND t.status=1`, h)
	var t Tenant
	if err := row.Scan(&t.ID, &t.Name, &t.Status, &t.Subnet, &t.CreatedAt); err != nil {
		return nil, err
	}
	return &t, nil
}

func (s *Store) GetUserByTenant(tenantID int64) (*User, error) {
	row := s.DB.QueryRow(`SELECT id,tenant_id,role,email,password_hash,status,created_at FROM users WHERE tenant_id=? ORDER BY id LIMIT 1`, tenantID)
	var u User
	if err := row.Scan(&u.ID, &u.TenantID, &u.Role, &u.Email, &u.PasswordHash, &u.Status, &u.CreatedAt); err != nil {
		return nil, err
	}
	return &u, nil
}

func (s *Store) GetUserByToken(token string) (*User, error) {
	h := hashTokenString(token)
	row := s.DB.QueryRow(`SELECT u.id,u.tenant_id,u.role,u.email,u.password_hash,u.status,u.created_at FROM api_keys k JOIN users u ON k.tenant_id=u.tenant_id WHERE k.key_hash=? AND k.status=1`, h)
	var u User
	if err := row.Scan(&u.ID, &u.TenantID, &u.Role, &u.Email, &u.PasswordHash, &u.Status, &u.CreatedAt); err != nil {
		return nil, err
	}
	return &u, nil
}

func (s *Store) GetUserByEmail(tenantID int64, email string) (*User, error) {
	row := s.DB.QueryRow(`SELECT id,tenant_id,role,email,password_hash,status,created_at FROM users WHERE tenant_id=? AND email=? ORDER BY id LIMIT 1`, tenantID, email)
	var u User
	if err := row.Scan(&u.ID, &u.TenantID, &u.Role, &u.Email, &u.PasswordHash, &u.Status, &u.CreatedAt); err != nil {
		return nil, err
	}
	return &u, nil
}

func (s *Store) CreateUser(tenantID int64, role, email, password string, status int) (*User, error) {
	now := time.Now().Unix()
	var hash string
	if password != "" {
		b, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
		if err != nil {
			return nil, err
		}
		hash = string(b)
	}
	res, err := s.DB.Exec(`INSERT INTO users(tenant_id,role,email,password_hash,status,created_at) VALUES(?,?,?,?,?,?)`, tenantID, role, email, hash, status, now)
	if err != nil {
		return nil, err
	}
	id, _ := res.LastInsertId()
	return &User{ID: id, TenantID: tenantID, Role: role, Email: email, PasswordHash: hash, Status: status, CreatedAt: now}, nil
}

func (s *Store) UserEmailExists(tenantID int64, email string) (bool, error) {
	row := s.DB.QueryRow(`SELECT COUNT(1) FROM users WHERE tenant_id=? AND email=?`, tenantID, email)
	var c int
	if err := row.Scan(&c); err != nil {
		return false, err
	}
	return c > 0, nil
}

func (s *Store) VerifyUserPassword(tenantID int64, email, password string) (*User, error) {
	u, err := s.GetUserByEmail(tenantID, email)
	if err != nil {
		// compatibility: allow login by role name (admin/operator)
		row := s.DB.QueryRow(`SELECT id,tenant_id,role,email,password_hash,status,created_at FROM users WHERE tenant_id=? AND role=? ORDER BY id LIMIT 1`, tenantID, email)
		var byRole User
		if scanErr := row.Scan(&byRole.ID, &byRole.TenantID, &byRole.Role, &byRole.Email, &byRole.PasswordHash, &byRole.Status, &byRole.CreatedAt); scanErr != nil {
			return nil, err
		}
		u = &byRole
	}
	if u.PasswordHash == "" {
		return nil, errors.New("password not set")
	}
	if err := bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(password)); err != nil {
		return nil, errors.New("invalid password")
	}
	return u, nil
}

func (s *Store) CreateSessionToken(userID, tenantID int64, role string, ttl time.Duration) (string, int64, error) {
	tok := randToken()
	h := hashTokenString(tok)
	now := time.Now().Unix()
	exp := time.Now().Add(ttl).Unix()
	_, err := s.DB.Exec(`INSERT INTO session_tokens(user_id,tenant_id,role,token_hash,expires_at,status,created_at) VALUES(?,?,?,?,?,1,?)`, userID, tenantID, role, h, exp, now)
	if err != nil {
		return "", 0, err
	}
	return tok, exp, nil
}

func (s *Store) VerifySessionToken(token string) (*SessionToken, error) {
	h := hashTokenString(token)
	now := time.Now().Unix()
	row := s.DB.QueryRow(`SELECT id,user_id,tenant_id,role,token_hash,expires_at,status,created_at FROM session_tokens WHERE token_hash=? AND status=1 AND expires_at>?`, h, now)
	var st SessionToken
	if err := row.Scan(&st.ID, &st.UserID, &st.TenantID, &st.Role, &st.TokenHash, &st.ExpiresAt, &st.Status, &st.CreatedAt); err != nil {
		return nil, err
	}
	return &st, nil
}

func (s *Store) RevokeSessionToken(token string) error {
	h := hashTokenString(token)
	_, err := s.DB.Exec(`UPDATE session_tokens SET status=0 WHERE token_hash=?`, h)
	return err
}

func (s *Store) ListUsers(tenantID int64) ([]User, error) {
	rows, err := s.DB.Query(`SELECT id,tenant_id,role,email,password_hash,status,created_at FROM users WHERE tenant_id=? ORDER BY id`, tenantID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	out := []User{}
	for rows.Next() {
		var u User
		if err := rows.Scan(&u.ID, &u.TenantID, &u.Role, &u.Email, &u.PasswordHash, &u.Status, &u.CreatedAt); err != nil {
			return nil, err
		}
		out = append(out, u)
	}
	return out, nil
}

func (s *Store) UpdateUserStatus(id int64, status int) error {
	_, err := s.DB.Exec(`UPDATE users SET status=? WHERE id=?`, status, id)
	return err
}

func (s *Store) UpdateUserPassword(id int64, password string) error {
	b, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return err
	}
	_, err = s.DB.Exec(`UPDATE users SET password_hash=? WHERE id=?`, string(b), id)
	return err
}

func hashTokenBytes(b []byte) string {
	h := sha256.Sum256(b)
	return hex.EncodeToString(h[:])
}

func randToken() string {
	b := make([]byte, 24)
	_, _ = rand.Read(b)
	return hex.EncodeToString(b)
}

func randUUID() string {
	b := make([]byte, 16)
	_, _ = rand.Read(b)
	b[6] = (b[6] & 0x0f) | 0x40
	b[8] = (b[8] & 0x3f) | 0x80
	return fmt.Sprintf("%s-%s-%s-%s-%s",
		hex.EncodeToString(b[0:4]),
		hex.EncodeToString(b[4:6]),
		hex.EncodeToString(b[6:8]),
		hex.EncodeToString(b[8:10]),
		hex.EncodeToString(b[10:16]),
	)
}

// helper to avoid unused import (net)
var _ = net.IPv4len
