package shares

import (
    "context"
    "database/sql"
    "encoding/json"
    "errors"
    "fmt"
    "github.com/google/uuid"
    "strings"

    "github.com/example/storage-appliance/internal/audit"
    "github.com/example/storage-appliance/internal/domain"
    "github.com/example/storage-appliance/internal/infra/nfs"
    "github.com/example/storage-appliance/internal/infra/samba"
)

var (
    ErrForbidden = errors.New("forbidden")
)

type SharesService struct {
    DB    *sql.DB
    NFS   *nfs.Adapter
    Samba *samba.Adapter
    Audit audit.AuditLogger
}

func NewSharesService(db *sql.DB, n *nfs.Adapter, s *samba.Adapter, a audit.AuditLogger) *SharesService {
    return &SharesService{DB: db, NFS: n, Samba: s, Audit: a}
}

func (s *SharesService) ListNFS(ctx context.Context) ([]domain.Share, error) {
    rows, err := s.DB.QueryContext(ctx, `SELECT id, name, path, type, options FROM shares WHERE type = 'nfs'`)
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    var res []domain.Share
    for rows.Next() {
        var id, name, path, typ, options string
        if err := rows.Scan(&id, &name, &path, &typ, &options); err != nil {
            return nil, err
        }
        var optMap map[string]string
        if options != "" {
            _ = json.Unmarshal([]byte(options), &optMap)
        }
        res = append(res, domain.Share{ID: domain.UUID(id), Name: name, Path: path, Type: typ})
    }
    return res, nil
}

// CreateNFS stores a new NFS export, re-renders /etc/exports and applies it
func (s *SharesService) CreateNFS(ctx context.Context, user, role, name, path string, opts map[string]string) (string, error) {
    if role != "admin" && role != "operator" {
        return "", ErrForbidden
    }
    // Verify path exists and is a dataset: check dataset table for matching name
    var count int
    if err := s.DB.QueryRowContext(ctx, `SELECT COUNT(1) FROM datasets WHERE name = ?`, path).Scan(&count); err != nil {
        return "", err
    }
    if count == 0 {
        return "", fmt.Errorf("path not a known dataset: %s", path)
    }
    // Prevent exporting system paths: disallow leading '/' entries; require dataset name like pool/ds
    if path == "/" || path == "/etc" || path == "/bin" || path == "/usr" {
        return "", fmt.Errorf("can't export system path: %s", path)
    }
    // store options as JSON
    optJSON, _ := json.Marshal(opts)
    id := uuid.New().String()
    if _, err := s.DB.ExecContext(ctx, `INSERT INTO shares (id, name, path, type, options) VALUES (?, ?, ?, 'nfs', ?)`, id, name, path, string(optJSON)); err != nil {
        return "", err
    }
    if s.Audit != nil {
        s.Audit.Record(ctx, audit.Event{UserID: user, Action: "nfs.create", ResourceType: "share", ResourceID: name, Success: true, Details: map[string]any{"path": path}})
    }
    // re-render exports
    shares, err := s.ListNFS(ctx)
    if err != nil {
        return id, err
    }
    if s.NFS != nil {
        if err := s.NFS.RenderExports(ctx, shares); err != nil {
            return id, err
        }
        if err := s.NFS.Apply(ctx); err != nil {
            return id, err
        }
    }
    return id, nil
}

// SMB functions
func (s *SharesService) ListSMB(ctx context.Context) ([]domain.Share, error) {
    rows, err := s.DB.QueryContext(ctx, `SELECT id, name, path, type, options FROM shares WHERE type = 'smb'`)
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    var res []domain.Share
    for rows.Next() {
        var id, name, path, typ, options string
        if err := rows.Scan(&id, &name, &path, &typ, &options); err != nil {
            return nil, err
        }
        var config map[string]string
        if options != "" {
            _ = json.Unmarshal([]byte(options), &config)
        }
        res = append(res, domain.Share{ID: domain.UUID(id), Name: name, Path: path, Type: typ, Config: config})
    }
    return res, nil
}

func (s *SharesService) CreateSMB(ctx context.Context, user, role, name, path string, readOnly bool, allowedUsers []string) (string, error) {
    if role != "admin" && role != "operator" {
        return "", ErrForbidden
    }
    // Verify dataset
    var count int
    if err := s.DB.QueryRowContext(ctx, `SELECT COUNT(1) FROM datasets WHERE name = ?`, path).Scan(&count); err != nil {
        return "", err
    }
    if count == 0 {
        return "", fmt.Errorf("path not a known dataset: %s", path)
    }
    // disallow system paths by basic checks
    if path == "/" || path == "/etc" || path == "/bin" || path == "/usr" {
        return "", fmt.Errorf("can't export system path: %s", path)
    }
    // store options as JSON (read_only, allowed_users)
    cfg := map[string]string{}
    if readOnly {
        cfg["read_only"] = "true"
    } else {
        cfg["read_only"] = "false"
    }
    if len(allowedUsers) > 0 {
        cfg["allowed_users"] = strings.Join(allowedUsers, " ")
    }
    optJSON, _ := json.Marshal(cfg)
    id := uuid.New().String()
    if _, err := s.DB.ExecContext(ctx, `INSERT INTO shares (id, name, path, type, options) VALUES (?, ?, ?, 'smb', ?)`, id, name, path, string(optJSON)); err != nil {
        return "", err
    }
    if s.Audit != nil {
        s.Audit.Record(ctx, audit.Event{UserID: user, Action: "smb.create", ResourceType: "share", ResourceID: name, Success: true, Details: map[string]any{"path": path, "read_only": readOnly}})
    }
    // re-render smb conf and reload
    shares, err := s.ListSMB(ctx)
    if err != nil {
        return id, err
    }
    if s.Samba != nil {
        if err := s.Samba.RenderConf(ctx, shares); err != nil {
            return id, err
        }
        if err := s.Samba.Reload(ctx); err != nil {
            return id, err
        }
    }
    return id, nil
}

func (s *SharesService) DeleteSMB(ctx context.Context, user, role, id string) error {
    if role != "admin" && role != "operator" {
        return ErrForbidden
    }
    if _, err := s.DB.ExecContext(ctx, `DELETE FROM shares WHERE id = ?`, id); err != nil {
        return err
    }
    if s.Audit != nil {
        s.Audit.Record(ctx, audit.Event{UserID: user, Action: "smb.delete", ResourceType: "share", ResourceID: id, Success: true})
    }
    shares, err := s.ListSMB(ctx)
    if err != nil {
        return err
    }
    if s.Samba != nil {
        if err := s.Samba.RenderConf(ctx, shares); err != nil {
            return err
        }
        if err := s.Samba.Reload(ctx); err != nil {
            return err
        }
    }
    return nil
}

func (s *SharesService) DeleteNFS(ctx context.Context, user, role, id string) error {
    if role != "admin" && role != "operator" {
        return ErrForbidden
    }
    // verify exists
    if _, err := s.DB.ExecContext(ctx, `DELETE FROM shares WHERE id = ?`, id); err != nil {
        return err
    }
    if s.Audit != nil {
        s.Audit.Record(ctx, audit.Event{UserID: user, Action: "nfs.delete", ResourceType: "share", ResourceID: id, Success: true})
    }
    // re-render exports
    shares, err := s.ListNFS(ctx)
    if err != nil {
        return err
    }
    if s.NFS != nil {
        if err := s.NFS.RenderExports(ctx, shares); err != nil {
            return err
        }
        if err := s.NFS.Apply(ctx); err != nil {
            return err
        }
    }
    return nil
}

func (s *SharesService) NFSStatus(ctx context.Context) (string, error) {
    if s.NFS == nil {
        return "unavailable", nil
    }
    return s.NFS.Status(ctx)
}
