package objectstore

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

    "github.com/example/storage-appliance/internal/audit"
    "github.com/example/storage-appliance/internal/infra/minio"
    "github.com/example/storage-appliance/internal/infra/crypto"
)

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

type Settings struct {
    ID        string
    Name      string
    AccessKey string
    SecretKey string
    DataPath  string
    Port      int
    TLS       bool
    CreatedAt time.Time
}

type ObjectService struct {
    DB    *sql.DB
    Minio *minio.Adapter
    Audit audit.AuditLogger
    // encryption key for secret storage
    Key   []byte
}

func NewObjectService(db *sql.DB, m *minio.Adapter, a audit.AuditLogger, key []byte) *ObjectService {
    return &ObjectService{DB: db, Minio: m, Audit: a, Key: key}
}

func (s *ObjectService) SetSettings(ctx context.Context, user, role string, stMap map[string]any) error {
    if role != "admin" {
        return ErrForbidden
    }
    // convert map to Settings struct for local use
    st := Settings{}
    if v, ok := stMap["access_key"].(string); ok { st.AccessKey = v }
    if v, ok := stMap["secret_key"].(string); ok { st.SecretKey = v }
    if v, ok := stMap["data_path"].(string); ok { st.DataPath = v }
    if v, ok := stMap["name"].(string); ok { st.Name = v }
    if v, ok := stMap["port"].(int); ok { st.Port = v }
    if v, ok := stMap["tls"].(bool); ok { st.TLS = v }

    // encrypt access key and secret key
    if len(s.Key) != 32 {
        return errors.New("encryption key must be 32 bytes")
    }
    encAccess, err := crypto.Encrypt(s.Key, st.AccessKey)
    if err != nil {
        return err
    }
    encSecret, err := crypto.Encrypt(s.Key, st.SecretKey)
    if err != nil {
        return err
    }
    // upsert into DB (single row)
    if _, err := s.DB.ExecContext(ctx, `INSERT OR REPLACE INTO object_storage (id, name, access_key, secret_key, data_path, port, tls) VALUES ('minio', ?, ?, ?, ?, ?, ?)` , st.Name, encAccess, encSecret, st.DataPath, st.Port, boolToInt(st.TLS)); err != nil {
        return err
    }
    if s.Audit != nil {
        s.Audit.Record(ctx, audit.Event{UserID: user, Action: "object.settings.update", ResourceType: "object_storage", ResourceID: "minio", Success: true})
    }
    if s.Minio != nil {
        // write env file
        settings := minio.Settings{AccessKey: st.AccessKey, SecretKey: st.SecretKey, DataPath: st.DataPath, Port: st.Port, TLS: st.TLS}
        if err := s.Minio.WriteEnv(ctx, settings); err != nil {
            return err
        }
        if err := s.Minio.Reload(ctx); err != nil {
            return err
        }
    }
    return nil
}

func (s *ObjectService) GetSettings(ctx context.Context) (map[string]any, error) {
    var st Settings
    row := s.DB.QueryRowContext(ctx, `SELECT name, access_key, secret_key, data_path, port, tls, created_at FROM object_storage WHERE id = 'minio'`)
    var encAccess, encSecret string
    var tlsInt int
    if err := row.Scan(&st.Name, &encAccess, &encSecret, &st.DataPath, &st.Port, &tlsInt, &st.CreatedAt); err != nil {
        return nil, err
    }
    st.TLS = tlsInt == 1
    if len(s.Key) == 32 {
        if A, err := crypto.Decrypt(s.Key, encAccess); err == nil { st.AccessKey = A }
        if S, err := crypto.Decrypt(s.Key, encSecret); err == nil { st.SecretKey = S }
    }
    res := map[string]any{"name": st.Name, "access_key": st.AccessKey, "secret_key": st.SecretKey, "data_path": st.DataPath, "port": st.Port, "tls": st.TLS, "created_at": st.CreatedAt}
    return res, nil
}

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

// ListBuckets via minio adapter or fallback to DB
func (s *ObjectService) ListBuckets(ctx context.Context) ([]string, error) {
    if s.Minio != nil {
        // ensure mc alias is configured
        stMap, err := s.GetSettings(ctx)
        if err != nil { return nil, err }
        alias := "appliance"
        mSet := minio.Settings{}
        if v, ok := stMap["access_key"].(string); ok { mSet.AccessKey = v }
        if v, ok := stMap["secret_key"].(string); ok { mSet.SecretKey = v }
        if v, ok := stMap["data_path"].(string); ok { mSet.DataPath = v }
        if v, ok := stMap["port"].(int); ok { mSet.Port = v }
        if v, ok := stMap["tls"].(bool); ok { mSet.TLS = v }
        s.Minio.ConfigureMC(ctx, alias, mSet)
        return s.Minio.ListBuckets(ctx, alias)
    }
    // fallback to DB persisted buckets
    rows, err := s.DB.QueryContext(ctx, `SELECT name FROM buckets`)
    if err != nil { return nil, err }
    defer rows.Close()
    var res []string
    for rows.Next() {
        var name string
        if err := rows.Scan(&name); err != nil { return nil, err }
        res = append(res, name)
    }
    return res, nil
}

func (s *ObjectService) CreateBucket(ctx context.Context, user, role, name string) (string, error) {
    if role != "admin" && role != "operator" { return "", ErrForbidden }
    // attempt via minio adapter
    if s.Minio != nil {
        stMap, err := s.GetSettings(ctx)
        if err != nil { return "", err }
        alias := "appliance"
        mSet := minio.Settings{}
        if v, ok := stMap["access_key"].(string); ok { mSet.AccessKey = v }
        if v, ok := stMap["secret_key"].(string); ok { mSet.SecretKey = v }
        if v, ok := stMap["data_path"].(string); ok { mSet.DataPath = v }
        if v, ok := stMap["port"].(int); ok { mSet.Port = v }
        if v, ok := stMap["tls"].(bool); ok { mSet.TLS = v }
        if err := s.Minio.ConfigureMC(ctx, alias, mSet); err != nil { return "", err }
        if err := s.Minio.CreateBucket(ctx, alias, name); err != nil { return "", err }
        // persist
        id := fmt.Sprintf("bucket-%d", time.Now().UnixNano())
        if _, err := s.DB.ExecContext(ctx, `INSERT INTO buckets (id, name) VALUES (?, ?)`, id, name); err != nil {
            return "", err
        }
        if s.Audit != nil { s.Audit.Record(ctx, audit.Event{UserID: user, Action: "object.bucket.create", ResourceType: "bucket", ResourceID: name, Success: true}) }
        return id, nil
    }
    return "", errors.New("no minio adapter configured")
}
