package storage

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

	_ "github.com/go-sql-driver/mysql"
	_ "github.com/jackc/pgx/v5/stdlib"
	_ "modernc.org/sqlite"

	"containr/internal/apwhy/auth"
	"containr/internal/apwhy/config"
	"containr/internal/apwhy/rbac"
)

type Store struct {
	DB  *sql.DB
	Cfg config.Config
}

func NowISO() string {
	return time.Now().UTC().Format(time.RFC3339)
}

func Open(cfg config.Config) (*Store, error) {
	db, err := sql.Open("sqlite", cfg.SQLitePath)
	if err != nil {
		return nil, err
	}
	db.SetMaxOpenConns(1)

	if _, err := db.Exec(schemaSQL); err != nil {
		return nil, fmt.Errorf("failed to apply schema: %w", err)
	}

	s := &Store{DB: db, Cfg: cfg}
	if err := s.seedAccessControl(context.Background()); err != nil {
		return nil, err
	}
	return s, nil
}

func (s *Store) Close() error {
	return s.DB.Close()
}

func (s *Store) seedAccessControl(ctx context.Context) error {
	tx, err := s.DB.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer tx.Rollback()

	now := NowISO()

	for _, permission := range rbac.PermissionSeeds {
		id, _ := auth.RandomID("perm")
		_, err := tx.ExecContext(ctx, `
			INSERT INTO permissions (id, code, name, description, created_at)
			VALUES (?, ?, ?, ?, ?)
			ON CONFLICT(code) DO UPDATE SET name=excluded.name, description=excluded.description
		`, id, permission.Code, permission.Name, permission.Description, now)
		if err != nil {
			return err
		}
	}

	type roleSeed struct {
		Name        string
		Slug        string
		Description string
		System      bool
		PermCodes   []string
	}

	roles := []roleSeed{
		{Name: "Owner", Slug: "owner", Description: "Primary administrator with all permissions.", System: true, PermCodes: rbac.OwnerPermissionCodes},
		{Name: "Admin", Slug: "admin", Description: "Operational admin with management permissions.", System: true, PermCodes: rbac.AdminPermissionCodes},
		{Name: "Viewer", Slug: "viewer", Description: "Read-only dashboard access.", System: true, PermCodes: rbac.ViewerPermissionCodes},
	}

	for _, role := range roles {
		roleID := ""
		_ = tx.QueryRowContext(ctx, `SELECT id FROM roles WHERE slug = ?`, role.Slug).Scan(&roleID)
		if roleID == "" {
			roleID, _ = auth.RandomID("role")
			_, err := tx.ExecContext(ctx, `
				INSERT INTO roles (id, name, slug, description, is_system, enabled, created_at, updated_at)
				VALUES (?, ?, ?, ?, ?, 1, ?, ?)
			`, roleID, role.Name, role.Slug, role.Description, boolToInt(role.System), now, now)
			if err != nil {
				return err
			}
		} else {
			_, err := tx.ExecContext(ctx, `
				UPDATE roles SET name = ?, description = ?, is_system = ?, updated_at = ? WHERE id = ?
			`, role.Name, role.Description, boolToInt(role.System), now, roleID)
			if err != nil {
				return err
			}
		}

		if role.System {
			_, err := tx.ExecContext(ctx, `DELETE FROM role_permissions WHERE role_id = ?`, roleID)
			if err != nil {
				return err
			}
			for _, code := range role.PermCodes {
				permID := ""
				if err := tx.QueryRowContext(ctx, `SELECT id FROM permissions WHERE code = ?`, code).Scan(&permID); err != nil {
					return err
				}
				_, err = tx.ExecContext(ctx, `
					INSERT INTO role_permissions (role_id, permission_id, created_at) VALUES (?, ?, ?)
					ON CONFLICT(role_id, permission_id) DO NOTHING
				`, roleID, permID, now)
				if err != nil {
					return err
				}
			}
		}
	}

	return tx.Commit()
}

func boolToInt(value bool) int {
	if value {
		return 1
	}
	return 0
}

func monthPeriod(t time.Time) string {
	return t.UTC().Format("2006-01")
}

func slugify(value string, fallback string) string {
	v := strings.ToLower(strings.TrimSpace(value))
	if v == "" {
		v = fallback
	}
	out := strings.Builder{}
	lastDash := false
	for _, r := range v {
		if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
			out.WriteRune(r)
			lastDash = false
			continue
		}
		if !lastDash {
			out.WriteRune('-')
			lastDash = true
		}
	}
	result := strings.Trim(out.String(), "-")
	if result == "" {
		return fallback
	}
	return result
}

func normalizePathPrefix(value, fallback string) string {
	v := strings.TrimSpace(value)
	if v == "" {
		v = fallback
	}
	if !strings.HasPrefix(v, "/") {
		v = "/" + v
	}
	if len(v) > 1 && strings.HasSuffix(v, "/") {
		v = strings.TrimSuffix(v, "/")
	}
	return v
}

func normalizeHealthPath(value string) string {
	return normalizePathPrefix(value, "/health")
}

func parseAllowedServiceIDs(value string) []string {
	if strings.TrimSpace(value) == "" {
		return []string{}
	}
	var result []string
	_ = json.Unmarshal([]byte(value), &result)
	if result == nil {
		return []string{}
	}
	return result
}

func mustJSON(v any) string {
	bytes, _ := json.Marshal(v)
	return string(bytes)
}

func minLimit(a, b sql.NullInt64) sql.NullInt64 {
	if !a.Valid && !b.Valid {
		return sql.NullInt64{}
	}
	if !a.Valid {
		return b
	}
	if !b.Valid {
		return a
	}
	if a.Int64 < b.Int64 {
		return a
	}
	return b
}

func toNullInt(value *int) sql.NullInt64 {
	if value == nil || *value <= 0 {
		return sql.NullInt64{}
	}
	return sql.NullInt64{Valid: true, Int64: int64(*value)}
}

func scanJSONText(value sql.NullString) string {
	if !value.Valid {
		return "[]"
	}
	if strings.TrimSpace(value.String) == "" {
		return "[]"
	}
	return value.String
}

var ErrNotFound = errors.New("not found")
