package sqlite

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"time"

	"switchboard-core/models"
	"switchboard-core/store"
)

// ── GroupStore ──────────────────────────────

type GroupStore struct{}

func NewGroupStore() *GroupStore { return &GroupStore{} }

func (s *GroupStore) Create(ctx context.Context, g *models.Group) error {
	g.ID = store.NewID()
	now := time.Now().UTC()
	g.CreatedAt = now
	g.UpdatedAt = now
	if g.Source == "" {
		g.Source = "manual"
	}
	if g.Permissions == nil {
		g.Permissions = []string{}
	}
	permsJSON, err := json.Marshal(g.Permissions)
	if err != nil {
		return fmt.Errorf("marshal permissions: %w", err)
	}
	_, err = DB.ExecContext(ctx, `
		INSERT INTO groups (id, name, description, scope, team_id, created_by, source, permissions, created_at, updated_at)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		g.ID, g.Name, g.Description, g.Scope,
		models.NullString(g.TeamID), models.NullString(g.CreatedBy), g.Source, string(permsJSON),
		now.Format(timeFmt), now.Format(timeFmt),
	)
	return err
}

func (s *GroupStore) GetByID(ctx context.Context, id string) (*models.Group, error) {
	var g models.Group
	var teamID sql.NullString
	var createdBy sql.NullString
	var permsStr sql.NullString

	err := DB.QueryRowContext(ctx, `
		SELECT g.id, g.name, g.description, g.scope, g.team_id, g.created_by,
		       g.created_at, g.updated_at,
		       (SELECT COUNT(*) FROM group_members WHERE group_id = g.id) AS member_count,
		       COALESCE(g.source, 'manual'),
		       COALESCE(g.permissions, '[]')
		FROM groups g WHERE g.id = ?`, id).Scan(
		&g.ID, &g.Name, &g.Description, &g.Scope, &teamID, &createdBy,
		st(&g.CreatedAt), st(&g.UpdatedAt), &g.MemberCount, &g.Source,
		&permsStr,
	)
	if err != nil {
		return nil, err
	}
	g.TeamID = NullableStringPtr(teamID)
	g.CreatedBy = NullableStringPtr(createdBy)
	g.Permissions = unmarshalStringSlice(permsStr.String)
	return &g, nil
}

// Update applies a patch to a group.
// System groups allow permission edits but not structural field changes.
func (s *GroupStore) Update(ctx context.Context, id string, patch models.GroupPatch) error {
	var source string
	if err := DB.QueryRowContext(ctx, "SELECT source FROM groups WHERE id = ?", id).Scan(&source); err != nil {
		return err
	}

	b := NewUpdate("groups")
	if source != "system" {
		if patch.Name != nil {
			b.Set("name", *patch.Name)
		}
		if patch.Description != nil {
			b.Set("description", *patch.Description)
		}
	}
	if patch.Permissions != nil {
		permsJSON, err := json.Marshal(*patch.Permissions)
		if err != nil {
			return fmt.Errorf("marshal permissions: %w", err)
		}
		b.Set("permissions", string(permsJSON))
	}
	if !b.HasSets() {
		return nil
	}
	b.Where("id", id)
	res, err := b.Exec(DB)
	if err != nil {
		return err
	}
	if n, _ := res.RowsAffected(); n == 0 {
		return sql.ErrNoRows
	}
	return nil
}

// Delete removes a group. Returns an error if source=system.
func (s *GroupStore) Delete(ctx context.Context, id string) error {
	var source string
	if err := DB.QueryRowContext(ctx, "SELECT source FROM groups WHERE id = ?", id).Scan(&source); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return sql.ErrNoRows
		}
		return err
	}
	if source == "system" {
		return store.ErrSystemGroup
	}
	res, err := DB.ExecContext(ctx, "DELETE FROM groups WHERE id = ?", id)
	if err != nil {
		return err
	}
	if n, _ := res.RowsAffected(); n == 0 {
		return sql.ErrNoRows
	}
	return nil
}

// ── Scoped Listing ──────────────────────────

const groupSelectCols = `
	g.id, g.name, g.description, g.scope, g.team_id, g.created_by,
	g.created_at, g.updated_at,
	(SELECT COUNT(*) FROM group_members WHERE group_id = g.id) AS member_count,
	COALESCE(g.source, 'manual'),
	COALESCE(g.permissions, '[]')`

func (s *GroupStore) ListAll(ctx context.Context) ([]models.Group, error) {
	return queryGroups(ctx, `SELECT `+groupSelectCols+`
		FROM groups g
		ORDER BY g.scope, g.name`)
}

func (s *GroupStore) ListForTeam(ctx context.Context, teamID string) ([]models.Group, error) {
	return queryGroups(ctx, `SELECT `+groupSelectCols+`
		FROM groups g
		WHERE g.scope = 'team' AND g.team_id = ?
		ORDER BY g.name`, teamID)
}

func (s *GroupStore) ListForUser(ctx context.Context, userID string) ([]models.Group, error) {
	return queryGroups(ctx, `SELECT `+groupSelectCols+`
		FROM groups g
		WHERE g.id IN (SELECT group_id FROM group_members WHERE user_id = ?)
		ORDER BY g.scope, g.name`, userID)
}

// ── Members ─────────────────────────────────

func (s *GroupStore) AddMember(ctx context.Context, groupID, userID, addedBy string) error {
	_, err := DB.ExecContext(ctx, `
		INSERT INTO group_members (id, group_id, user_id, added_by)
		VALUES (?, ?, ?, ?)
		ON CONFLICT (group_id, user_id) DO NOTHING`,
		store.NewID(), groupID, userID, addedBy)
	return err
}

func (s *GroupStore) RemoveMember(ctx context.Context, groupID, userID string) error {
	res, err := DB.ExecContext(ctx,
		"DELETE FROM group_members WHERE group_id = ? AND user_id = ?",
		groupID, userID)
	if err != nil {
		return err
	}
	if n, _ := res.RowsAffected(); n == 0 {
		return sql.ErrNoRows
	}
	return nil
}

func (s *GroupStore) ListMembers(ctx context.Context, groupID string) ([]models.GroupMember, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT gm.id, gm.group_id, gm.user_id, gm.added_by, gm.added_at,
		       u.username, u.email, COALESCE(u.display_name, '')
		FROM group_members gm
		JOIN users u ON u.id = gm.user_id
		WHERE gm.group_id = ?
		ORDER BY u.username`, groupID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var result []models.GroupMember
	for rows.Next() {
		var m models.GroupMember
		if err := rows.Scan(&m.ID, &m.GroupID, &m.UserID, &m.AddedBy, st(&m.AddedAt),
			&m.Username, &m.Email, &m.DisplayName); err != nil {
			return nil, err
		}
		result = append(result, m)
	}
	return result, rows.Err()
}

func (s *GroupStore) IsMember(ctx context.Context, groupID, userID string) (bool, error) {
	var exists bool
	err := DB.QueryRowContext(ctx, `
		SELECT EXISTS(SELECT 1 FROM group_members WHERE group_id = ? AND user_id = ?)`,
		groupID, userID).Scan(&exists)
	return exists, err
}

func (s *GroupStore) GetUserGroupIDs(ctx context.Context, userID string) ([]string, error) {
	rows, err := DB.QueryContext(ctx,
		"SELECT group_id FROM group_members WHERE user_id = ?", userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var ids []string
	for rows.Next() {
		var id string
		if err := rows.Scan(&id); err != nil {
			return nil, err
		}
		ids = append(ids, id)
	}
	return ids, rows.Err()
}

// ── Scanners ────────────────────────────────

func queryGroups(ctx context.Context, q string, args ...interface{}) ([]models.Group, error) {
	rows, err := DB.QueryContext(ctx, q, args...)
	if err != nil {
		return nil, fmt.Errorf("queryGroups: %w", err)
	}
	defer rows.Close()

	var result []models.Group
	for rows.Next() {
		var g models.Group
		var teamID sql.NullString
		var createdBy sql.NullString
		var permsStr sql.NullString

		if err := rows.Scan(
			&g.ID, &g.Name, &g.Description, &g.Scope, &teamID, &createdBy,
			st(&g.CreatedAt), st(&g.UpdatedAt), &g.MemberCount, &g.Source,
			&permsStr,
		); err != nil {
			return nil, err
		}
		g.TeamID = NullableStringPtr(teamID)
		g.CreatedBy = NullableStringPtr(createdBy)
		g.Permissions = unmarshalStringSlice(permsStr.String)
		result = append(result, g)
	}
	return result, rows.Err()
}

func unmarshalStringSlice(s string) []string {
	if s == "" {
		return []string{}
	}
	var out []string
	if err := json.Unmarshal([]byte(s), &out); err != nil {
		return []string{}
	}
	return out
}
